# Village Governance API — OpenAPI 3.0 (Track B P4). PUBLISHABLE — wire contract only; reveals no
# internals by design. Generated from the as-built V1 (proven live on devtest). Serve on the Village
# public site / agenticgovernance.digital docs, mirroring the Tractatus openapi.yaml publication.
openapi: 3.0.3
info:
  title: Village Governance API
  version: "1.0"
  description: >
    Governance-as-a-service. Submit a proposed action (optionally with the full kōrero — the
    deliberation, human engagement acts, and AI inputs that fed it). Receive a verdict
    (allow / gate / deny) and a signed, customer-owned receipt. The kōrero is stored as an
    immutable sovereign record you can retrieve in full and verify offline via did:web.
    Sovereign, non-US (NZ/EU), Ed25519-signed. The wire never carries how a verdict was reached.
  contact: { name: My Digital Sovereignty Ltd, url: https://mysovereignty.digital }
  license: { name: Proprietary }
servers:
  - url: https://{tenant}/api/v1
    variables: { tenant: { default: sandbox.mysovereignty.digital } }
security:
  - bearerKey: []
components:
  securitySchemes:
    bearerKey: { type: http, scheme: bearer, bearerFormat: "vk_ tenant-scoped API key" }
  schemas:
    Action:
      type: object
      required: [type]
      properties:
        type: { type: string, description: "the caller's verb (we classify; we don't dictate)", example: email.send }
        summary: { type: string, maxLength: 500 }
        target: { type: object, properties: { kind: { type: string }, ref: { type: string } } }
        payload_sha256: { type: string, description: "the caller's hash of their full action payload; bound by the receipt" }
        content: { type: string, description: "optional text to evaluate, ≤8KB; encrypted at rest" }
        reversible: { type: boolean }
        actor: { type: object, properties: { kind: { type: string }, ref: { type: string } } }
    Korero:
      type: object
      description: The customer's deliberation, verbatim. Stored immutably, encrypted at rest, owned by the customer.
      properties:
        proposal: { type: object, properties: { title: { type: string, maxLength: 300 }, description: { type: string } } }
        participants:
          type: array
          items: { type: object, required: [ref], properties: { ref: { type: string }, role_label: { type: string }, did: { type: string } } }
        entries:
          type: array
          items:
            type: object
            properties:
              seq: { type: integer }
              at: { type: string, format: date-time }
              participant_ref: { type: string }
              kind: { type: string, enum: [statement, question, challenge, response, amendment, summary] }
              body: { type: string }
              refs: { type: array, items: { type: string } }
        ai_inputs:
          type: array
          items:
            type: object
            required: [ref]
            properties: { ref: { type: string }, tool: { type: string }, model: { type: string }, prompt_sha256: { type: string }, output_sha256: { type: string }, output_summary: { type: string } }
        engagements:
          type: array
          items:
            type: object
            required: [participant_ref, act, reasons]
            properties:
              participant_ref: { type: string }
              act: { type: string, enum: [reviewed, challenged, accepted_with_reasons, rejected, requested_changes, flagged, abstained] }
              ai_input_ref: { type: string }
              reasons: { type: string, description: "the human's OWN words; never AI-drafted" }
              at: { type: string, format: date-time }
        outcome_claim: { type: string, maxLength: 1000 }
    Receipt:
      type: object
      description: Signed (Ed25519) receipt. Binds hashes, not content — leaks nothing if it circulates. Verify offline via did:web.
      properties:
        receipt_version: { type: integer, example: 2 }
        receipt_id: { type: string }
        decision_id: { type: string }
        tenant_did: { type: string }
        request_sha256: { type: string }
        action_payload_sha256: { type: string }
        korero_sha256: { type: string }
        korero_segment_count: { type: integer }
        decision: { type: string, enum: [allow, gate, deny] }
        reason_codes: { type: array, items: { $ref: '#/components/schemas/ReasonCode' } }
        policy_epoch: { type: string }
        decided_at: { type: string, format: date-time }
        algorithm: { type: string, example: Ed25519 }
        signer_did: { type: string }
        signature: { type: string, description: hex }
    ReasonCode:
      type: string
      enum: [HUMAN_REVIEW_REQUIRED, IRREVERSIBLE_ACTION, SECURITY_SENSITIVE, FINANCIAL_THRESHOLD, INSTRUCTION_CONFLICT, CULTURAL_CONTEXT, CONTENT_UNVERIFIED, CONTEXT_INTEGRITY, TENANT_POLICY, SCOPE_EXCEEDED, UNSPECIFIED]
    Verdict:
      type: object
      properties:
        decision_id: { type: string }
        decision: { type: string, enum: [allow, gate, deny] }
        reason_codes: { type: array, items: { $ref: '#/components/schemas/ReasonCode' } }
        policy_epoch: { type: string }
        decided_at: { type: string, format: date-time }
        gate: { type: object, properties: { status: { type: string, enum: [pending, resolved, expired] }, poll: { type: string }, expires_at: { type: string, format: date-time } } }
        receipt: { $ref: '#/components/schemas/Receipt' }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, enum: [invalid_request, unauthorised, forbidden_scope, tenant_mismatch, payload_too_large, rate_limited, decision_not_found, receipt_invalid_format, unavailable] }
            message: { type: string }
  responses:
    Err: { description: Error, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
paths:
  /governance/verdicts:
    post:
      summary: Request a verdict (thin action, or action + rich kōrero)
      description: "Scope governance:evaluate. Idempotent by idempotency_key within 24h."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action: { $ref: '#/components/schemas/Action' }
                context: { type: object, properties: { conversation_ref: { type: string }, tags: { type: array, items: { type: string } }, requested_by: { type: string } } }
                korero: { $ref: '#/components/schemas/Korero' }
                idempotency_key: { type: string }
      responses:
        '200': { description: Verdict, content: { application/json: { schema: { $ref: '#/components/schemas/Verdict' } } } }
        '400': { $ref: '#/components/responses/Err' }
        '429': { $ref: '#/components/responses/Err' }
        '503': { $ref: '#/components/responses/Err' }
  /governance/verdicts/{decision_id}:
    get:
      summary: Poll a gated decision (thin — no kōrero)
      parameters: [ { name: decision_id, in: path, required: true, schema: { type: string } } ]
      responses:
        '200': { description: Decision status, content: { application/json: { schema: { $ref: '#/components/schemas/Verdict' } } } }
        '404': { $ref: '#/components/responses/Err' }
  /governance/verdicts/{decision_id}/korero:
    post:
      summary: Append a kōrero segment while the decision is gated (pending)
      parameters: [ { name: decision_id, in: path, required: true, schema: { type: string } } ]
      requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { korero: { $ref: '#/components/schemas/Korero' } } } } } }
      responses:
        '200': { description: Segment appended + append receipt, content: { application/json: { schema: { type: object, properties: { decision_id: { type: string }, korero_segment_count: { type: integer }, receipt: { $ref: '#/components/schemas/Receipt' } } } } } }
        '404': { $ref: '#/components/responses/Err' }
        '409': { $ref: '#/components/responses/Err' }
  /governance/verdicts/{decision_id}/record:
    get:
      summary: The full customer-owned dossier + dump (kōrero decrypted for its owner, verdict, receipts, proof chain)
      description: "Scope governance:record. Portable + offline-verifiable with the DID document."
      parameters: [ { name: decision_id, in: path, required: true, schema: { type: string } } ]
      responses:
        '200': { description: Full record dossier }
        '404': { $ref: '#/components/responses/Err' }
  /governance/receipts/verify:
    post:
      summary: Verify a presented receipt (offline via did:web is equivalent + preferred)
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [receipt], properties: { receipt: { $ref: '#/components/schemas/Receipt' }, korero_canonical: { type: string } } } } } }
      responses:
        '200': { description: Verification result, content: { application/json: { schema: { type: object, properties: { valid: { type: boolean }, checks: { type: object }, verified_at: { type: string, format: date-time } } } } } }
        '400': { $ref: '#/components/responses/Err' }
  /governance/policy:
    get:
      summary: Vocabulary + limits (no rules, no policy content)
      responses: { '200': { description: Policy metadata } }
  /governance/usage:
    get:
      summary: The caller's own current-period usage, tier, and quota
      responses: { '200': { description: Usage } }
