{
  "schema_version": "1.0",
  "baseline": "A-document-access-service-v2",
  "phase": "Batch 12 · gating envelope",
  "updated_at": "2026-04-20",
  "owner": "session_a",
  "purpose_en": "Contract for /api/access/gate — the server-side gating envelope consumed by the Universal Document Shell. Replaces client-side computation of render_mode/allow flags with a single authoritative server call.",
  "purpose_th": "สัญญาของ /api/access/gate · ให้ server ตัดสินใจแทน client ว่า render อะไร · share/export ได้ไหม",
  "honest_note": "Envelope-level enforcement only. The server does NOT intercept HTML delivery. Only shelled pages that actually call /api/access/gate participate.",
  "endpoint": {
    "method": "GET",
    "path": "/api/access/gate",
    "query": {
      "doc_id": "string (required)",
      "token": "string (optional · else cookie / bearer)",
      "include_stub": "bool (optional · default false)"
    },
    "auth": "Cookie ds_session OR ?token=… OR Authorization: Bearer"
  },
  "response_envelope": {
    "doc_id": "string",
    "group_id": "string | null",
    "state": "visible | restricted | hidden-doc | hidden-group | not-granted",
    "allow_render": "bool · true when state in (visible, restricted)",
    "render_mode": "full | restricted | blocked",
    "allow_read": "bool",
    "allow_share": "bool",
    "allow_export": "bool",
    "reason_en": "string | null",
    "reason_th": "string | null",
    "stub_html": "string | null · present only when include_stub=true and render_mode=blocked",
    "profile_id": "string",
    "email": "string",
    "mode": "local-dev",
    "resolved_at": "ISO-8601",
    "honest_banner": { "en": "string", "th": "string" },
    "honest_scope": "string · documents that gating is envelope-level"
  },
  "state_to_render_mode": {
    "visible":      { "render_mode": "full",       "allow_read": true,  "allow_share": true,  "allow_export": true  },
    "restricted":   { "render_mode": "restricted", "allow_read": true,  "allow_share": false, "allow_export": false },
    "hidden-doc":   { "render_mode": "blocked",    "allow_read": false, "allow_share": false, "allow_export": false },
    "hidden-group": { "render_mode": "blocked",    "allow_read": false, "allow_share": false, "allow_export": false },
    "not-granted":  { "render_mode": "blocked",    "allow_read": false, "allow_share": false, "allow_export": false }
  },
  "stub_html_notes": {
    "when_returned": "only when include_stub=true AND render_mode=blocked",
    "format": "self-contained HTML fragment · inline styles · no external CSS · renders even if shell assets are unreachable",
    "contents": "title + state badge + bilingual reason + doc_id + back-to-console link · intentionally minimal",
    "size_budget": "under 4 KB"
  },
  "client_contract": {
    "when_called": "on DOMContentLoaded for pages with class='ds-page' that load document-shell.js",
    "on_blocked": "shell replaces document.body innerHTML with stub_html (body-only slice)",
    "on_restricted": "shell displays the banner and disables share/copy/print buttons",
    "on_full": "no change",
    "on_error": "leave page as-is · shell already applied Batch-7/8 UI-layer behavior"
  },
  "non_goals_this_batch": [
    "HTTP-level interception of static HTML delivery",
    "Reverse-proxy / nginx gating rules",
    "Caching of gate decisions",
    "Audit trail of gate decisions",
    "Per-section gating within a single doc"
  ],
  "cross_references": {
    "access_contract":  "docs/runtime/document-access-service/access_contract.json",
    "gating_examples":  "docs/runtime/document-access-service/gating_examples.json",
    "planning":         "docs/planning/document-server-gating.html",
    "auth_hardening":   "docs/planning/document-auth-hardening.html"
  }
}
