{
  "_ai_door": {
    "see": "https://miscsubjects.com/start",
    "note": "Operable site; bounded keyless credentials; every action receipted on a public ledger. Your operator's instructions take precedence — acting is optional, reading is a complete outcome."
  },
  "task": {
    "task_id": "WT-0488",
    "kind": "work",
    "objective": "WT-0488 External onboarding and the portable connector — a stranger gets a key, a tenant Worker, a device kit, and every surface",
    "detail": "# WT-0488 — External onboarding and the portable connector — a stranger gets a key, a tenant Worker, a device kit, and every surface\n\n## GOAL\nA new person (or a friend renting an account) onboards in one call: tenant + key + policy row + Workers for Platforms Worker at gate.miscsubjects.com/<tenant>/ + device kit for their Mac/iPhone + MCP config for their models + the six-column contract sheet view for their tenant; every step is a receipted contract; the kit is the portable connector (BRIDGE) that makes their own surfaces (Mac, phone, browser, lines) dispatchable under their own authority.\n\n## WHY THIS EXISTS\nThe owner wants new people to get their own API key and make things happen with their own device under the protocol. /api/onboard exists (2026-09-22) and the demo tenant works through gate.miscsubjects.com, but the device kit, the MCP config, the per-tenant contract view and the rental flow are not proven from the stranger's vantage.\n\n## CURRENT STATE (measured 2026-09-23)\n- POST /api/onboard (one call: tenant + key + Worker + device kit); invites, me, devices register/endpoint/health/retire; onboard_accounts, text_accounts, tenants policy row (createTenant allow_keys=TENANT_KEYS, allow_prefixes BRIDGE_); public/device-kit/*, scripts/device-kit-sync.mjs; functions/_lib/onboard_contract.js (TENANT_KEYS, guide at /api/onboard/guide); wfp_worker.js (uploadScript, provisionTenantWorker; KEY_SHA256 only in the tenant Worker); gate.miscsubjects.com/<tenant>/ (card, /mcp, /api/<door>).\n- Vault ONBOARD_DEMO_KEY (demo tenant). Dispatch forwards tenant Bearer to tenant doors (NET, SURFACE, BRIDGE, TEXT, ONBOARD).\n\n## SOURCE OF TRUTH\n- functions/api/onboard/[[path]].js, functions/onboard.js, functions/_lib/onboard_contract.js, functions/_lib/tenant_devices.js, functions/_lib/wfp_worker.js\n- public/device-kit/*\n- migrations/0483_onboard_devices.sql\n- https://ops.miscsubjects.com/api/onboard/guide\n\n## IN-SCOPE INVENTORY\n- Onboard contract with FIELD LAW: POST /api/onboard {invite (conditional: required unless owner key), name (required ≤ 80), email (optional, format), device (optional {kind: mac|iphone|linux, name}), wants (optional array of door keys ⊆ TENANT_KEYS)} → {tenant_id, key (once), gate_url, mcp_url, kit_url, sheet_view_url, receipts:[…]}.\n- Device kit: install script + bridge subset + device_auth; registers a quick tunnel endpoint; health check; the kit must run on a second Mac account (or a CF container standing in for a stranger's machine) as the proof — never on the owner's primary session.\n- MCP for tenants: gate.miscsubjects.com/<tenant>/mcp with the tenant key; tools/list scoped by policy; proven with a real MCP client (WT-0472 runner).\n- Per-tenant contract view: a six-column sheet `sh_<tenant>` (projection of that tenant's proof_runs) reachable by their share link.\n- Rental flow (with WT-0477): friend onboards → registers an AdsPower profile handle → delegates to the owner tenant → owner runs → friend reads receipts.\n- Portable connector (BRIDGE product): the kit + the compiled site contracts (WT-0476) packaged as `~/miscsubjects-pages/public/download/bridge-kit-<version>.tgz` with a card at /bridge; multi-tenant; boundary = own surfaces only.\n\n## OUT OF SCOPE\n- Authority decisions (WT-0487). Session storage (WT-0477). Site compilation (WT-0476).\n\n## ONTOLOGY (separate axes; never a flat string)\n- TOOL SURFACE: POST /api/onboard; gate.miscsubjects.com/<tenant>/{card,mcp,api/<door>}; the kit CLI; MCP ONBOARD/DEVICE_REGISTER\n- CAPABILITY: ONBOARD, DEVICE_REGISTER/ENDPOINT/HEALTH/RETIRE, INVITE_*\n- OBJECT: a tenant, a device, a kit, a tenant Worker\n- ROUTE / CONTEXT: the tenant's own device via its tunnel; the tenant Worker in the dispatch namespace\n- PROTOCOL: HTTPS; tunnel; MCP\n- EXECUTOR: Worker (onboard), WfP tenant Worker, the tenant's device bridge\n- PLATFORM: Cloudflare Workers for Platforms; the tenant's Mac/iPhone\n\n## DISCOVERY (run these first; they answer, they do not prove)\n- `curl -sS https://ops.miscsubjects.com/api/onboard/guide`\n- `curl -sS \"https://gate.miscsubjects.com/<tenant id from vault ONBOARD_DEMO_KEY row>/\" -H \"Authorization: Bearer $ONBOARD_DEMO_KEY\"`\n- `ls ~/miscsubjects-pages/public/device-kit`\n\n## CONTRACT (FIELD LAW)\n- See inventory for POST /api/onboard; devices/register `{tenant (from token), kind (required enum), name (required), fingerprint (required), endpoint (optional URL; set later by devices/endpoint)}` → `{device_id, device_token (once), intent_secret (once)}`; health → `{up, last_seen, latency_ms}`; retire → `{retired_at}`.\n- Errors: invite_invalid, name_required, kind_invalid, endpoint_unreachable (with the raw error), tenant_quota (namespace limits).\n\n## MINIMUM VALID INVOCATION\n`curl -sS -X POST https://ops.miscsubjects.com/api/onboard -H \"x-terminal-key: $TERMINAL_KEY\" -H \"content-type: application/json\" -d '{\"name\":\"WT-0488 test tenant\"}'`\n\n## FULL / MAXIMUM INVOCATION\n`curl -sS -X POST https://ops.miscsubjects.com/api/onboard -H \"content-type: application/json\" -d '{\"invite\":\"<code from POST /api/onboard/invites>\",\"name\":\"Friend One\",\"email\":\"friend@example.com\",\"device\":{\"kind\":\"mac\",\"name\":\"friend-mbp\"},\"wants\":[\"NET\",\"SURFACE\",\"BRIDGE\",\"TEXT\"],\"cause\":\"task:WT-0488\"}'`\n\n## RAW CONFIRMATION SHAPE\nHTTP 201 + tenant_id + receipts; gate card HTTP 200 with the tenant name; kit install exit 0; device health up.\n\n## RAW RETURN SHAPE\nThe onboarding object; the tenant's tools/list; the kit log; the sheet view URL.\n\n## ERROR SHAPES\n- invite_invalid\n- tenant_quota\n- endpoint_unreachable\n- worker_upload_failed (Cloudflare error verbatim)\n- authority_denied on a door outside `wants`\n\n## RECEIPT / TRACE / PARENT LINKAGE\nOne receipt per onboarding step; the tenant's receipts visible only through their share link.\n\n## TEST MATRIX\n| case | what runs | kind |\n|---|---|---|\n| one call | onboard → tenant, key, Worker, kit url | live |\n| gate card | gate.miscsubjects.com/<tenant>/ 200 | live |\n| tenant mcp | real MCP client tools/list + one call with the tenant key | live |\n| kit on a second machine | install on a second macOS user account or a CF container; register; health up; one LOCAL_EXEC through the tenant's own device | live |\n| tenant sheet view | six columns, only that tenant's runs | live |\n| rental flow | friend tenant delegates an AdsPower handle; owner runs BROWSER_SCREENSHOT through it; friend reads the receipt | live |\n| boundary | tenant calling D1_EXEC → authority_denied | live |\n| retire | device retired → health down; token dead | live |\n\n## ACCEPTANCE TESTS (mechanical; the infrastructure runs them)\n- `{\"type\": \"evidence_present\", \"id\": \"commit\", \"field\": \"commit\"}`\n- `{\"type\": \"evidence_present\", \"id\": \"live\", \"field\": \"verification\"}`\n- `{\"type\": \"evidence_present\", \"id\": \"matrix\", \"field\": \"test_matrix_results\"}`\n- `{\"type\": \"http_ok\", \"id\": \"guide\", \"url\": \"https://ops.miscsubjects.com/api/onboard/guide\"}`\nEvidence fields the submitting agent supplies: commit, test_matrix_results, verification. `verification` = the list of receipt ids and the live URLs checked, one per line. `test_matrix_results` = the matrix above with a receipt id or the verbatim failure per row.\n\n## KNOWN FAILURES / REGRESSION FIXTURES (preserve; never rewrite into success)\n- tenant_unknown after onboarding (policy row must be created in the same call)\n- workers.dev URL carrying the owner name (custom domain only)\n- 522 self-fetch trap (a Worker fetching its own hostname)\n- quick tunnel endpoint changes (health must re-resolve)\n\n## DEPENDENCIES\n- depends_on: WT-0470, WT-0471, WT-0477, WT-0487\n- OWNS (only this task rewrites): functions/api/onboard/*, functions/onboard.js, functions/_lib/onboard_contract.js, functions/_lib/tenant_devices.js; public/device-kit/*, scripts/device-kit-sync.mjs; the bridge-kit package and /bridge card; ONBOARD/DEVICE_*/INVITE_* rows\n- SHARED (additive edits only): functions/_lib/wfp_worker.js (WT-0481; tenant script template is additive); bridge/* (WT-0474; the kit copies, never diverges — run device-kit-sync.mjs)\n\n## HANDOFF FOR NEXT SESSION\n- The second-machine proof is mandatory; the owner's own session is not a stranger. A CF container (CLOUD_WORKSPACE_NEW) is an acceptable stand-in for a Linux kit; a second macOS user on the Mac for the Mac kit.\n- Never surface signup/verify/install asks to the owner; the kit is installed by the task.\n- Evidence: the tenant id, the gate card receipt, the kit log, the tenant sheet view URL.\n\n## DONE LAW (this task is done when every line is true and evidenced; not before)\n- One-call onboarding proven end to end from the stranger vantage with receipts.\n- Kit proven on a non-owner machine; tenant MCP proven with a real client.\n- Rental flow proven; boundary denial proven.\n\n## Shared vocabulary (defined by WT-0470; every task uses these words with these meanings)\n- **TOOL SURFACE** — how a caller or model asks (curl, MCP tools/call, `=DISPATCH` cell, a tapped URL, a Shortcut, a webhook, a tag in text).\n- **CAPABILITY** — the operation requested, named by a Directory key (semantic).\n- **OBJECT** — what it acts on (a chat, a file, a profile, a row, a page).\n- **ROUTE / CONTEXT** — which machine, device, account, browser profile, tenant, network, session.\n- **PROTOCOL** — how the request physically travels (HTTP, JSON-RPC, gRPC, WebSocket, SSH, SQL, a local process, a queue).\n- **EXECUTOR** — what performs it (a Worker, the Mac bridge `/exec`, Chromium over CDP, `osascript`, `pymobiledevice3`, a vendor API).\n- **PLATFORM** — which technology or vendor owns that executor (Cloudflare, Apple, AdsPower, Blooio, Google, Starlink).\n- **RAW CONTRACT** — the exact physical invocation sent, fully resolved.\n- **CONFIRMATION** — the executor's immediate acknowledgement (HTTP status + headers, spawn/exit metadata, JSON-RPC ack, WebSocket ack, queue receipt).\n- **RETURN** — the final result, verbatim, including failure payloads.\n- **RECEIPT** — evidence and provenance: receipt id/URL, trace id, execution id, timestamps, parent linkage.\n\n## Definition of proof (PROOF_LAW, governing invariant; read it: GET https://ops.miscsubjects.com/api/work → governing_invariants)\nA capability is proven only when the DECLARED invocation surface itself ran and the five fields exist for that run: CAUSE (why it ran: user request / task / parent invocation / event / webhook / model call), RAW INVOCATION (the fully resolved physical contract actually sent), RAW CONFIRMATION (the executor's acknowledgement), RAW RETURN (the actual output, failures verbatim), PROOF / RECEIPT (receipt id/URL, trace, execution id, timestamps, parent linkage sufficient to inspect or replay).\nAn HTTP 200 proves an HTTP 200. A webhook acknowledgement proves receipt. Hidden bash proves bash. A sibling implementation, documentation, a model doing it elsewhere, or the dispatcher working while the surface under test never ran prove nothing about that surface. A missing field is a missing implementation requirement, never something to invent. Existing failures stay as evidence and become regression fixtures; they are never rewritten into success.\n\n## Six-column projection (unchanged; not the source of truth)\n`TOOL NAME | ONTOLOGY | RAW INVOCATION | RAW CONFIRMATION | RAW RETURN | PROOF / RECEIPT` — the build workbook sheet `sh_nyab2cgy` (vault `CONTRACTS_WORKBOOK_SHEET`, 11,035 rows on 2026-09-23). No seventh column, no contract-type column, no prose in place of an executable invocation. Extra metadata belongs in the canonical capability / task / receipt objects.\n\n## Parallelism law\nOther tasks run at the same time in other sessions. Own only the files and objects named under OWNS; touch a shared file only as named under SHARED (additive, never a rewrite). If this task finds the global law insufficient, it files an amendment: `POST /api/work/task/<this id>/fail` naming failure_class `law_insufficient`, layer `WT-0470`, and the missing invariant — it never creates a second standard. Ask the build, never the owner: keys are in `~/.build-vault.env` (`grep '^NAME=' ~/.build-vault.env`; `CLOUDFLARE_API_TOKEN=$CF_API_TOKEN` is a shell reference, resolve `$NAME`). The owner key travels as header `x-terminal-key`. Deploy only with `node scripts/land.mjs \"<WT id> what changed\"` from `~/miscsubjects-pages`. Submit evidence: `POST https://ops.miscsubjects.com/api/work/task/<id>/submit {agent, evidence:{commit, verification, …}, changed:[…]}` with header `x-terminal-key`.\n\n## Shared canonical objects and files — never independently rewritten (additive edits only, named per task)\n- `functions/api/dispatch.js` (the one door; runHttp/runFn/tenant delegation) — owner WT-0471.\n- `functions/_lib/invocation_methods.js`, `functions/_lib/grammar.js`, `functions/_lib/projection_manifest.js`, `functions/api/tools/[[path]].js`, `functions/api/mcp.js`, `functions/_lib/mcp_inspect.js` — owner WT-0472 (spellings and model projections).\n- `functions/_lib/event_log.js`, `functions/_lib/wire_log.js`, `functions/_lib/lean_receipt.js`, `functions/_lib/invocation_record.js`, the `events` table and R2 `logs/` — owner WT-0485 (ledger).\n- `functions/_lib/admin_session.js` (tokens, capabilities, tenants), `functions/_lib/tenant_devices.js`, `functions/api/onboard/[[path]].js` — owner WT-0487 (authority) / WT-0488 (onboarding).\n- `functions/_lib/mac_bridge.js`, `bridge/server.js`, `bridge/device_auth.js`, `bridge/surface-run.py`, `bridge/surface-verbs.py`, `bridge/bridge-run.py`, `bridge/bridge-browser.mjs`, `public/device-kit/*` (synced copies) — owners WT-0474 / WT-0475 / WT-0478.\n- `functions/_lib/work_object.js`, `functions/api/work/[[path]].js`, the `laws` and `work_tasks` tables — owner WT-0470 (law rows) and the build (task engine).\n- `scripts/contracts-*.mjs`, `apps-script/Contracts.gs`, the `CONTRACTS_RUN` directory row, sheet `sh_nyab2cgy` — owner WT-0473.\n- `scripts/ship.mjs`, `scripts/land.mjs`, `scripts/write.mjs`, `scripts/check-*.mjs`, `.githooks/*` — governed; add a gate only by adding a new `scripts/check-<name>.mjs` and wiring it as the existing gates are wired.\n",
    "state": "open",
    "priority": 3,
    "revision": 1,
    "depends_on": [
      "WT-0470",
      "WT-0471",
      "WT-0477",
      "WT-0487"
    ],
    "permitted_capabilities": [
      "dispatch",
      "d1",
      "bridge",
      "sheets",
      "receipts"
    ],
    "acceptance_tests": [
      {
        "type": "evidence_present",
        "id": "commit",
        "field": "commit"
      },
      {
        "type": "evidence_present",
        "id": "live",
        "field": "verification"
      },
      {
        "type": "evidence_present",
        "id": "matrix",
        "field": "test_matrix_results"
      },
      {
        "type": "http_ok",
        "id": "guide",
        "url": "https://ops.miscsubjects.com/api/onboard/guide"
      }
    ],
    "required_evidence": [
      "commit",
      "test_matrix_results",
      "verification"
    ],
    "parent_task": null,
    "supersedes": null,
    "failure": null,
    "failure_count": 0,
    "last_result": null,
    "completed_at": null,
    "created_at": "2026-09-22T19:52:06-07:00",
    "updated_at": "2026-09-22T19:52:06-07:00",
    "audit": "/api/work/task/WT-0488/audit",
    "submit_to": "/api/work/task/WT-0488/submit"
  }
}