{
  "_readme": [
    "The connections Docket can make, and why. This file is the source; security.html's table is",
    "rendered from it by hand and checked against it by hosts-lint.js, which runs in `npm test` AND",
    "in the Vercel build. A deploy fails if the page and this file disagree in either direction.",
    "",
    "It lives under site/ on purpose: it ships publicly, so a reader can diff it against what their",
    "own firewall shows instead of taking the page's word for it. That is the whole argument of the",
    "page: 'generated from the code, not from memory' was false while the table was hand-written",
    "with nothing asserting it.",
    "",
    "MEASURED 2026-08-31 against server.js, main.js, update.js and licence.js. A naive grep for",
    "https:// literals is not a substitute: the code also contains example.com, jira.yourcompany.com",
    "and half a dozen link targets that are never fetched. Those are listed under `notFetched` so the",
    "lint can tell the difference."
  ],
  "self": [
    {
      "host": "api.gumroad.com",
      "why": "Checks your licence key is still valid.",
      "when": "At most once a day",
      "sends": "Your licence key, the product id, and a yes or no on whether to count this Mac. Nothing else.",
      "fields": { "license_key": "licence key", "product_id": "product id", "increment_uses_count": "count this Mac" },
      "note": "licence.js rate-limits refresh() to 24h (RECHECK_MS); the 6-hour timer in server.js only wakes it, so a Mac that sleeps does not skip its window. Offline keeps working for 14 days. The yes or no is the form field increment_uses_count. It is yes only on the first check for a key on this Mac. Gumroad then adds one to that key's use count. That count is how a team licence sees how many Macs use it. Every later check sends no. Gumroad's default is yes, so we always send the field: leaving it out would count every daily check. test/licence-request-truth.test.js checks that this list equals the request."
    },
    {
      "host": "docketmac.com",
      "why": "Reads one small static file naming the current version and where to download it.",
      "when": "Once, shortly after launch",
      "sends": "Nothing. No licence key, no device id, not even the version you are running: the comparison happens on your Mac after the file arrives.",
      "note": "This is a static file on the same host that serves this page. It is the closest thing to a Docket server that exists, and it is named here rather than denied."
    },
    {
      "host": "github.com",
      "why": "Resolves the release the feed named.",
      "when": "Only when an update is being downloaded",
      "sends": "Nothing about you.",
      "note": "timreerink-del/docket-releases: a repository holding release artifacts and no source, so the updater needs no credential."
    },
    {
      "host": "objects.githubusercontent.com",
      "why": "Where the update file itself actually downloads from.",
      "when": "Only when an update is being downloaded",
      "sends": "Nothing about you.",
      "note": "GitHub redirects release assets here. Naming github.com without naming this is naming the doorway and not the room."
    },
    {
      "host": "huggingface.co",
      "why": "Downloads the speech model that transcribes your meetings on your Mac.",
      "when": "Once, the first time you record or dictate",
      "sends": "Nothing about you.",
      "size": "~574 MB",
      "note": "The model runs entirely on your Mac afterwards. This is the one large download Docket makes, and the audio never leaves."
    },
    {
      "host": "cdn.hf.co",
      "wildcard": true,
      "why": "Where huggingface.co redirects that download.",
      "when": "Once, with the model download",
      "sends": "Nothing about you.",
      "note": "Measured 31 Aug: huggingface.co 302s to us.aws.cdn.hf.co. The subdomain is regional, so this is deliberately a suffix rather than one host. Your firewall may show a different region than ours."
    }
  ],
  "connected": [
    {
      "host": "api.anthropic.com",
      "service": "Claude API",
      "why": "Drafts text you asked for, if you gave Docket an API key.",
      "note": "Only reachable with a key you entered. By default the AI runs on this Mac only. This host is reached only once the AI engine card in Sources allows an external engine."
    },
    {
      "host": "generativelanguage.googleapis.com",
      "service": "Gemini",
      "why": "Drafts text you asked for, if you gave Docket an API key.",
      "note": "Only reachable with a key you entered. By default the AI runs on this Mac only. This host is reached only once the AI engine card in Sources allows an external engine."
    },
    {
      "host": "api.linear.app",
      "service": "Linear",
      "catalogue": true,
      "why": "Reads the issues assigned to you, and writes back the comments, status changes, estimates, priorities, assignees and new tickets you send or approve. The Linear catalogue template reads the same issues and writes nothing.",
      "note": "Only reachable with a personal API key you entered, through the Linear connector or the template you add in Sources. GraphQL, so it is one endpoint rather than many: your firewall will show a single host doing all of it."
    },
    {
      "host": "api.figma.com",
      "service": "Figma",
      "why": "Reads comments that mention you on files your tickets link to, and posts the replies you write.",
      "note": "Reachable only with a credential you supplied: either a personal access token you pasted, or a scoped sign-in you granted. The sign-in exchanges its one-time code here and renews itself here; nothing else is sent."
    },
    {
      "host": "api.miro.com",
      "service": "Miro",
      "why": "Reads the boards you point it at.",
      "note": "Which boards a token can see depends on your Miro plan."
    },
    {
      "host": "dovetail.com",
      "service": "Dovetail",
      "why": "Reads research highlights.",
      "note": "Only with a token you entered."
    },
    {
      "host": "api.github.com",
      "service": "GitHub (catalogue template)",
      "catalogue": true,
      "why": "Reads the issues or pull requests assigned to you, if you added a GitHub template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "gitlab.com",
      "service": "GitLab (catalogue template)",
      "catalogue": true,
      "why": "Reads the issues or merge requests assigned to you, if you added a GitLab template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "app.asana.com",
      "service": "Asana (catalogue template)",
      "catalogue": true,
      "why": "Reads the incomplete tasks assigned to you in one workspace, if you added the Asana template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "sentry.io",
      "service": "Sentry (catalogue template)",
      "catalogue": true,
      "why": "Reads the unresolved issues assigned to you in one organization, if you added the Sentry template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "api.notion.com",
      "service": "Notion (catalogue template)",
      "catalogue": true,
      "why": "Reads the pages assigned to you in one database, if you added the Notion template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "api.trello.com",
      "service": "Trello (catalogue template)",
      "catalogue": true,
      "why": "Reads the open cards you are a member of, if you added the Trello template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "api.todoist.com",
      "service": "Todoist (catalogue template)",
      "catalogue": true,
      "why": "Reads your open tasks, leaving out ones assigned to others, if you added the Todoist template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "api.clickup.com",
      "service": "ClickUp (catalogue template)",
      "catalogue": true,
      "why": "Reads the open tasks assigned to you in one Workspace, if you added the ClickUp template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "api.monday.com",
      "service": "monday.com (catalogue template)",
      "catalogue": true,
      "why": "Reads the items assigned to you on one board, if you added the monday.com template in Sources.",
      "note": "A template you switch on yourself; nothing reaches this host until you add one with your own token. Read only."
    },
    {
      "host": "oauth2.googleapis.com",
      "service": "Google Calendar",
      "why": "Google's OAuth endpoint.",
      "edition": "private",
      "note": "No OAuth client ships publicly, so a normal install cannot reach this. If you see it without having built your own client, that is a bug worth reporting."
    },
    {
      "host": "www.googleapis.com",
      "service": "Google Calendar",
      "why": "Reads calendar events.",
      "edition": "private",
      "note": "Same as above: unreachable on a normal install. Customers' calendars come from the Mac's own Calendar app via EventKit, which makes no network call of ours."
    },
    {
      "host": "<your Jira or Confluence host>",
      "userSupplied": true,
      "service": "Jira, Confluence",
      "why": "Reads your tickets and pages, and writes back what you send or approve.",
      "note": "Whatever address you entered. Docket has no default and ships with none."
    },
    {
      "host": "<your mail server>",
      "userSupplied": true,
      "service": "Mail",
      "why": "Reads message headers over IMAP.",
      "note": "Headers, not bodies, unless you open one."
    },
    {
      "host": "<your GitLab host>",
      "userSupplied": true,
      "catalogue": true,
      "templates": ["gitlab-self-hosted-issues", "gitlab-self-hosted-merge-requests"],
      "service": "GitLab, self-hosted (catalogue template)",
      "why": "Reads the issues or merge requests assigned to you on your own GitLab, if you added a self-hosted GitLab template in Sources.",
      "note": "Whatever address you entered. Docket has no default and ships with none. Read only."
    }
  ],
  "notFetched": {
    "_why": "https:// literals in the shipping code that are NOT connections. The lint knows these so it can fail on a genuinely new host instead of on a placeholder or a link.",
    "placeholders": [
      "example.com",
      "api.example.com",
      "jira.example.com",
      "jira.yourcompany.com",
      "yourcompany.atlassian.net"
    ],
    "linkTargets": [
      "linear.app",
      "www.figma.com",
      "miro.com",
      "mail.google.com",
      "calendar.google.com",
      "docs.google.com",
      "accounts.google.com"
    ],
    "_linkNote": "These appear in SRC_URL, the map behind each source card's 'Open ↗' button, in the provider seam's url() builders, and, for www.figma.com and accounts.google.com, as the vendor's own authorize page. An authorize page belongs here rather than under `connected` because Docket does not fetch it: it hands the URL to /usr/bin/open and your browser goes there, which is the whole point of an authorization-code flow and the reason no credential of yours passes through this app. The token exchange that follows IS a fetch, and it is on api.figma.com, which is named under `connected` (COC-362). Docket never calls them; your browser goes there when you click. linear.app is the issue URL a Linear row links to. api.linear.app, the host Docket fetches for Linear, is named under `connected`: the Linear connector (COC-193) reads your issues from its one GraphQL endpoint and writes back only what you send or approve, through six verbs (comment, assign, transition, create, estimate, priority) and no work log. This manifest describes the public edition only: a host the public build cannot contact is not listed anywhere in it, not even here (COC-560).",
    "discussed": [
      "googleapis.com"
    ],
    "_discussedNote": "Parent domains the page names in order to EXPLAIN something, without ever contacting them. The page says 'two rows mention googleapis.com, for unrelated reasons' precisely to argue why the list names subdomains: Gemini and Google Calendar are different things under one parent. That sentence is worth keeping, so the lint learned the category rather than the copy dodging it. A connector that is not in the public edition has no host here at all (COC-560): test/edition.test.js asserts the public build carries no such string, and test/hosts-manifest.test.js reads the code through the same public strip, so a private-edition host never needs an excuse in this file.",
    "removed": [
      "api.lemonsqueezy.com"
    ],
    "_removedNote": "Lemon Squeezy was cancelled in favour of Gumroad. The dead code path was deleted 31 Aug; this entry exists so the lint fails if it ever comes back."
  },
  "_catalogueNote": "Entries under connected with \"catalogue\": true are the hosts of Docket's bundled connector templates (catalogue.json in the app). Each is reached only after the user adds that template in Sources with their own token, so they are user-enabled and never in self. test/hosts-manifest.test.js fails if a template's host is missing here, listed in self, or listed here without a template behind it.",
  "neverExists": [
    "analytics",
    "crash reporting",
    "a telemetry endpoint of any kind"
  ],
  "_neverNote": "'A Docket server of any kind' used to be on this list and had to come off: docketmac.com serves the update feed. One static file, no account, no data of yours, but it is a host we control, and a list that denies it is worth less than a list that names it.",
  "local": [
    {
      "entry": "docket:// links",
      "reach": "Any web page, mail, chat message or app on this Mac, without the token.",
      "can": "Bring Docket forward on a view; put a question in Ask without sending it; after you click Keep in a dialog shown every time, leave a card in Needs you or change the current frame.",
      "cannot": "Write to any service, read anything back, open another link, or run a command. Five links a minute, five cards from outside at most, and none of them pushes your own cards out of Needs you."
    },
    {
      "entry": "the docket command",
      "reach": "Programs running as you that can read Docket's config file, which Docket keeps at mode 600.",
      "can": "What Docket's own window can, through the same token: a capture becomes a card in Needs you, frame start sets the current frame, ask prints an answer.",
      "cannot": "Take its token from the command line, or send it to a server that cannot prove it already holds the same token."
    }
  ],
  "_localNote": "COC-224. Not hosts: neither entry opens a network connection, so hosts-lint.js reads self and connected and never this list. They are listed because they are new ways in on the Mac itself, and test/front-door-site.test.js fails if security.html stops naming either one."
}
