{
  "version": 1,
  "owner": "P2HS Core — immutable. Style the UI; do not change these URLs or invent a second backend.",
  "client": {
    "nuxt_app": "composables/useP2hs.ts",
    "react_app": "src/lib/p2hs-app.js",
    "html": "js/p2hs-app.js"
  },
  "recipes": {
    "submit_form": {
      "use": "submitContact({ name, email, phone, message, website: '', turnstileToken })",
      "route": "POST /api/contact",
      "notes": [
        "website is a hidden honeypot. Do not POST to Resend, Nodemailer, or a homemade functions/api/contact.js.",
        "Owner notification mail is sent by that route. Do not add a second mailer.",
        "Install contact-capture via p2hs/feature-packs.json; put every named notify address in config.notify_to_email."
      ]
    },
    "send_email": {
      "use": "sendEmail({ to, subject, text, html? }, idempotencyKey)",
      "route": "POST /api/jobs/email.send",
      "auth": "staff Bearer",
      "notes": [
        "Always send Idempotency-Key. Poll job.poll_url if status is not terminal.",
        "Contact-form owner mail is already handled by POST /api/contact — do not duplicate it here."
      ]
    },
    "pull_records": {
      "use": "listRecords(entityKey) / getRecord(entityKey, id) / createRecord(entityKey, data) / patchRecord(entityKey, id, data)",
      "route": "/api/records/:entity",
      "auth": "staff Bearer unless the entity is public-read (owner_managed list)",
      "notes": [
        "Reuse database.entities keys. Never invent SQLite, KV, JSON files, or useStorage as source of truth.",
        "Contact inbox entity key is exactly contact_submission."
      ]
    },
    "contact_inbox": {
      "use": "listInbox()",
      "route": "GET /api/contact/inbox",
      "auth": "staff Bearer"
    },
    "magic_link": {
      "request": "requestMagicLink(email, { adminOnly: true }) from the staff URL",
      "request_route": "POST /api/auth/magic-link/request",
      "consume_route": "GET /api/auth/magic-link/consume?token=…&next=…",
      "notes": [
        "The emailed URL is always the consume route. Never create /admin/magic or put that path in the email.",
        "Consume stores access_token in localStorage then redirects to next (/admin, /be-admin, …).",
        "Staff fetches send Authorization: Bearer <access_token>."
      ]
    },
    "staff_dashboard": {
      "path": "/admin (or the path the user named, e.g. /be-admin)",
      "gate": "me() then require role admin or super_admin",
      "login": "If me().user is null, show email field and requestMagicLink",
      "data": "listInbox() and/or listRecords(entityKey)",
      "notes": [
        "One auth system. No local password table, user-db.ts, or hardcoded admin password.",
        "Invite additional staff with the staff-admin-console pack, not a homemade users table."
      ]
    }
  },
  "forbidden": [
    "p2hsClient / Cloud gateway calls from the browser",
    "/admin/magic landing pages",
    "Resend, Nodemailer, SMTP, or mailto-only as the contact backend",
    "local JSON/SQLite/KV as the records or users database",
    "Turnstile keys in required-user-inputs.json"
  ]
}
