{
  "_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-0477",
    "kind": "work",
    "objective": "WT-0477 Accounts, sessions, AdsPower profiles and delegated (friend / rented) accounts — handles, never authority",
    "detail": "# WT-0477 — Accounts, sessions, AdsPower profiles and delegated (friend / rented) accounts — handles, never authority\n\n## GOAL\nEvery account the build can act through (owner accounts, vault credentials, browser profiles, AdsPower profiles with proxies, rented ad accounts, delegated friend accounts) is a stored SESSION HANDLE with an owner, a scope, an expiry and a route, referenced by handle in invocations; a handle never implies authority (WT-0487 decides). Renting an ad account = a friend delegates a profile/session to a tenant for a scope and a time, and every use is receipted.\n\n## WHY THIS EXISTS\nThe owner asked how AdsPower profiles work so people can rent him ad accounts. Today credentials live in the vault (117 lines, CREDS index), AdsPower has one Default Profile, proxies are a TSV, and nothing records who may use which session for what. Sessions and authority are conflated everywhere.\n\n## CURRENT STATE (measured 2026-09-23)\n- Vault ~/.build-vault.env (CREDS index memory: 117 creds, 12 authed CLIs); ACCESS.md names locations.\n- AdsPower: Local API (Bearer, 1 req/2 s), bridge/adspower.sh + adspower_profile.conf + proxies.tsv (AdsPower + proxy stack memory); one profile.\n- LEDGER `capabilities` table: token rows with tenant_id, device_id, fingerprint, scope (authority, WT-0487).\n- onboard_accounts / text_accounts tables from /api/onboard (2026-09-22).\n- No `sessions` table; cookies exported by browsers have no home.\n\n## SOURCE OF TRUTH\n- ~/miscsubjects-pages/ACCESS.md\n- ~/.build-vault.env (values never in chat; names only)\n- bridge/adspower*.sh, bridge/adspower_profile.conf, bridge/proxies.tsv\n- functions/_lib/admin_session.js\n- AdsPower Local API docs\n\n## IN-SCOPE INVENTORY\n- Table `session_handles` (LEDGER DB): handle (id), kind ∈ {vault_cred, cookie_jar, oauth_token, adspower_profile, chrome_profile, phone_line, api_key}, owner_principal, delegated_to (tenant|null), scope (JSON allow list of keys/prefixes), route (JSON: profile id, proxy, device), expires_at, storage_ref (vault name | R2 key encrypted with a KV-held key | AdsPower profile id), created_by, receipts_count.\n- Verbs: SESSION_LIST, SESSION_MINT (from a browser export, WT-0475 cookies_export), SESSION_DELEGATE {handle, to_tenant, scope, ttl}, SESSION_REVOKE, SESSION_USE (resolver-internal: inject into a physical invocation and receipt it).\n- AdsPower as routes: ADSPOWER_PROFILE_CREATE {name, proxy (from proxies.tsv line id), fingerprint preset}, ADSPOWER_PROFILE_DELEGATE (= SESSION_DELEGATE with kind adspower_profile), and the rental flow: friend registers a profile under their principal → delegates to the owner's tenant → the owner's runs go through that profile with receipts the friend can read (share link scoped to their handle).\n- Vault index page (owner-only, behind Access): names + where used + last used; values never rendered.\n\n## OUT OF SCOPE\n- Deciding allow/deny (WT-0487). Driving the browser (WT-0475). Compiling sites (WT-0476). Phone lines' sending (WT-0480).\n\n## ONTOLOGY (separate axes; never a flat string)\n- TOOL SURFACE: POST /api/dispatch SESSION_* and ADSPOWER_* rows; MCP CAP_MINT (existing) stays for authority\n- CAPABILITY: SESSION_LIST/MINT/DELEGATE/REVOKE, ADSPOWER_PROFILE_*\n- OBJECT: a session handle; an AdsPower profile; a proxy line\n- ROUTE / CONTEXT: which profile + proxy + device the handle binds\n- PROTOCOL: HTTPS; AdsPower Local API over LAN from the Mac\n- EXECUTOR: Worker (handles), bridge (AdsPower)\n- PLATFORM: the build; AdsPower; proxy vendors\n\n## DISCOVERY (run these first; they answer, they do not prove)\n- `grep -c \"=\" ~/.build-vault.env`\n- `curl -sS -X POST https://ops.miscsubjects.com/api/dispatch -H \"x-terminal-key: $TERMINAL_KEY\" -H \"content-type: application/json\" -d '{\"key\":\"ADSPOWER_PROFILES\"}'`\n- `sed -n 1,5p ~/miscsubjects-pages/bridge/proxies.tsv | cut -f1,2` (never print the credential columns)\n\n## CONTRACT (FIELD LAW)\n- SESSION_DELEGATE `{handle (required), to_tenant (required, existing tenant id), scope (required, array of keys or prefixes, non-empty), ttl_s (required, 60..7776000), note (optional)}` → `{ok, delegation_id, expires_at, receipt_id}`; conflicts: delegating a handle you do not own → authority_denied; scope wider than the handle's own scope → scope_exceeds_handle.\n- ADSPOWER_PROFILE_CREATE `{name (required), proxy_line (required, id from proxies.tsv), fingerprint (optional preset id), group (optional)}` → AdsPower `{code:0, data:{id}}` verbatim as return; rate limit honoured (2 s).\n\n## MINIMUM VALID INVOCATION\n`curl -sS -X POST https://ops.miscsubjects.com/api/dispatch -H \"x-terminal-key: $TERMINAL_KEY\" -H \"content-type: application/json\" -d '{\"key\":\"SESSION_LIST\"}'`\n\n## FULL / MAXIMUM INVOCATION\n`curl -sS -X POST https://ops.miscsubjects.com/api/dispatch -H \"x-terminal-key: $TERMINAL_KEY\" -H \"content-type: application/json\" -d '{\"key\":\"SESSION_DELEGATE\",\"body\":{\"handle\":\"sh_adspower_default\",\"to_tenant\":\"<tenant id from ONBOARD me>\",\"scope\":[\"BROWSER_\",\"BRIDGE_\"],\"ttl_s\":86400,\"note\":\"rented ad account test\"},\"cause\":\"task:WT-0477\"}'`\n\n## RAW CONFIRMATION SHAPE\nHTTP 200 + delegation id; AdsPower `{\"code\":0,\"msg\":\"success\"}` or `{\"code\":-1,\"msg\":\"Too many request per second\"}` verbatim.\n\n## RAW RETURN SHAPE\nHandle rows (values never); AdsPower data verbatim.\n\n## ERROR SHAPES\n- authority_denied\n- scope_exceeds_handle\n- handle_expired\n- adspower_rate_limited (code -1)\n- adspower_unreachable (LAN only; from a Worker it is route_unavailable)\n\n## RECEIPT / TRACE / PARENT LINKAGE\nEvery SESSION_USE is a proof_run child of the delegation receipt; the friend's share link lists them.\n\n## TEST MATRIX\n| case | what runs | kind |\n|---|---|---|\n| list | SESSION_LIST shows vault names and the AdsPower profile, no values | live |\n| mint from browser | cookies_export of a build sign-in → handle | live |\n| delegate | delegate to ONBOARD_DEMO_KEY tenant, scope BROWSER_, 1 h | live |\n| use | tenant runs BROWSER_SCREENSHOT with the handle → receipt under the delegation | live |\n| revoke | revoke → next use authority_denied | live |\n| adspower create | a second profile with a proxies.tsv line | live |\n| rate limit | two calls < 2 s apart → code -1 recorded | fixture |\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\"}`\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- AdsPower unreachable from GAS/Worker\n- AdsPower 1 req/2 s\n- vault catalog leak 2026-09-19 (values must never render; test that SESSION_LIST output contains no `=`-separated secret)\n\n## DEPENDENCIES\n- depends_on: WT-0470, WT-0471, WT-0487\n- OWNS (only this task rewrites): functions/api/sessions/* (new); migrations/0485_session_handles.sql (new, LEDGER); bridge/adspower*.sh; SESSION_* and ADSPOWER_* rows\n- SHARED (additive edits only): functions/_lib/admin_session.js (WT-0487; call its allow/deny, add nothing); functions/_lib/resolver.js (WT-0471; SESSION_USE hook is additive)\n\n## HANDOFF FOR NEXT SESSION\n- Never print a vault value; the leak test is a deploy gate (scripts/check-vault-render.mjs exists from 2026-09-19 — extend it to the new door).\n- AdsPower calls go through the Mac bridge; from a Worker they are route_unavailable by design.\n- The rental demo uses the demo tenant (vault ONBOARD_DEMO_KEY).\n\n## DONE LAW (this task is done when every line is true and evidenced; not before)\n- session_handles exists and every account kind has ≥ 1 handle.\n- Delegate → use → revoke proven with receipts.\n- AdsPower profile create + delegate proven; rate limit fixture passes.\n- No value renders anywhere (gate).\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-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"
      }
    ],
    "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:51:58-07:00",
    "updated_at": "2026-09-22T19:51:58-07:00",
    "audit": "/api/work/task/WT-0477/audit",
    "submit_to": "/api/work/task/WT-0477/submit"
  }
}