# Human Gates producer and notification contract Human Gates is an account-bound release-candidate inbox. The canonical mobile route is `#/my-work/human-gates`. Reads may use the last account-scoped browser cache, but Release/Hold decisions require a live authenticated identity and an online server round trip. ## Producer intake Authenticated producers submit `POST /api/v1/human-gates/intake` with a unique `Idempotency-Key` header and JSON such as: ```json { "source": "release-bot", "project": "stackchain/dashboard", "candidate_hash": "abc123", "title": "Dashboard candidate", "priority": 7, "artifacts": [{"name": "manifest", "url": "https://forge.example/artifacts/manifest.json"}], "links": [{"label": "change", "url": "https://forge.example/pulls/1415"}], "checks": [{"name": "browser", "state": "success", "required": true}], "score": {"value": 92, "provenance": "release-evaluator/v2"}, "provenance": {"producer": "release-bot", "run_id": "run-9"} } ``` The immutable identity is authenticated account + `source` + `project` + `candidate_hash`. Retrying the same key and body returns the same gate. Reusing a key for different facts, or redefining an existing candidate hash, returns 409. A newer hash from the same source/project atomically marks older pending candidates `superseded`; detail history remains available for audit. Configure durable storage with `STACKCHAIN_HUMAN_GATE_DB` (default: `$STACKCHAIN_STATE_DIR/human-gates.sqlite3`). Consumers list `GET /api/v1/human-gates`, inspect `GET /api/v1/human-gates/{id}`, and submit `POST /api/v1/human-gates/{id}/decision` with a new `Idempotency-Key`, `expected_revision`, and either `release` or `hold`. Hold requires a reason. Release requires all three checklist confirmations; if any required check is not successful it also requires an explicit override reason. Durable receipts are available at `GET /api/v1/human-gate-receipts/{receipt_id}`. All endpoints are authenticated, account-bound, and `Cache-Control: no-store`. ## Telegram coalescing contract Telegram or lock-screen adapters MUST coalesce pending changes per authenticated account and expose **count and route only**: ```json { "pending_count": 3, "route": "#/my-work/human-gates" } ``` The notification text may say “3 Human Gates pending” and provide the route. It MUST NOT include project names, candidate hash values, titles, artifact/link URLs, checks, scores, provenance, decision history, superseded candidate facts, reasons, or receipts. Multiple intake/supersession events before delivery replace the pending notification with the latest count rather than emitting one message per candidate. A transition to zero clears the outstanding notification; it does not send candidate detail. Producers and adapters must fetch current state after authenticated route open rather than treating notification delivery as an action authorization.