You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
id: investigate: hosted data option — per-user stores, Google SSO, static/local-storage variants, container-spend limits #2045
LIST 12b. The hosted offer: maybe a SQLite per user who signs in with Google SSO — 'our way of having a cloud-hosted version that they can host their data in'. Investigate: per-user store provisioning and isolation, SSO→ID binding (how a Google identity relates to an Iroh ID), static sites using local storage and websites-with-backends as alternative shapes, and the cost guardrail — a little container use is acceptable, indefinite per-user-interaction container spend is not. Deliverable: hosted-shape comparison with rough per-user cost drivers.
Program: Drew's 2026-10-28 directive (full text: hatch-personal memory/topics/id-program-2026-10-28.md).
Working pattern: parallel investigations, then the named synthesis issue merges them into a full design. Route guidance: Workhorse by default; Specialist/Summit for synthesis and ramification-heavy work. A result that says "done" is not the endpoint — the orchestrator reviews it and may commission another round. Deliverables: a markdown design/investigation doc (mermaid/SVG diagrams welcome) linked from this issue, plus follow-up issues for anything buildable. Build a proof of concept where the design calls for one, but do NOT build the permanent feature on top of an unreviewed PoC.
Top priority — ID program wave-1. Drew directed this be picked up in the next runs (2026-10-29): enqueued directly as triage task 3354 in repo-health-code-pilot at queue priority 20 (only breakage-class work at priority 10 outranks it). Wave-1 is the ungated investigations and designs; syntheses and PoCs stay gated on their named inputs. Investigation route: Workhorse.
Investigation result — hosted data option (#2045, queue task 3354)
Verdict
The hosted offer is viable in exactly the shape Drew sketched — per-user store, Google SSO sign-in, no per-user containers — and the #2044 Workers-fit investigation already settled the platform half: one Durable Object per user (SQLite-backed, strongly consistent, serialized per user, ~1,000 req/s soft cap per object, 10 GB per object on Paid) is the per-user server + database, costs nothing while idle, and the hosted edition is necessarily an HTTP/WebSocket-shaped subset because Workers have no UDP for Iroh transport. This investigation covers what #2044 did not: provisioning and isolation, the SSO→ID binding (the subtle part), the alternative shapes, and the cost guardrails.
Provisioning and isolation
Lazy provisioning: first Google sign-in creates the account record and names the user's Durable Object (idFromName over the binding ID); the DO and its SQLite schema materialize on first use. No sign-up-time fleet of anything; an account that never returns costs one small account row.
Isolation is structural, not query discipline: one user's objects live in one DO's storage; there is no shared table in which a missing WHERE user = ? leaks across users. Cross-user interaction never touches another user's store directly — it moves signed graph objects through the platform's normal exchange surfaces (shared worlds, messages, public stores), where the receiving store applies its own ingest rules (id: investigate: trust/visibility model — servers accept all signed objects; views count only allowed writers #2030: validity at ingest, counting at view). A hosted user and a self-hosted peer are indistinguishable at the object layer.
Blobs: large content goes to R2 (content-addressed, deduplicated across users — identical blobs stored once), with per-user reference rows in their SQLite. DO SQLite rows cap at 2 MB (id: investigate: Cloudflare Workers edition — isolates + Durable Objects + per-user SQLite fit for ID #2044), so this split is forced anyway; dedupe is safe because blobs are addressed by hash and private content is client-encrypted before upload (below), making stored blobs opaque.
SSO → ID binding (the part that must not be hand-waved)
Google identity and Iroh ID are different kinds of thing — one is an authentication relationship with Google, the other is a self-sovereign key — and the design keeps them in separate lanes:
The account record keys on Google's sub (the stable subject identifier), never the email address (emails change, get recycled, and are shared in ways sub is not). Email is stored, if at all, as contact metadata.
Binding ceremony: signed-in user proves control of their Iroh root key by signing a service-issued challenge with it (in-browser via the web client, or from their own node). The result is a binding record: {google sub, Iroh ID, bound at} countersigned by the Iroh key and attested by the service. Sign-in alone never creates a binding; a Google account with no key proof is an empty hosted account that can claim nothing.
Devices don't bind individually: the binding names the root/person ID; devices join through the platform's existing device-grant mechanism (groups layer), exactly as in the self-hosted model.
Recovery is where bindings are attacked, so it is deliberately slow: losing Google access is a Google problem (their recovery), and the binding survives it — regained Google access re-enters the same account. Losing the Iroh key is the hard case: Google sign-in alone must never silently rebind the account to a fresh key (that converts any Google account takeover into an ID takeover). Rebind requires the old key's signature, or a delayed rebind: request → notification to the old binding's contact points → waiting period (parameter; days, not minutes) → rebind, with the rebind itself published as a signed service record. Users who keep a self-hosted node holding their key never need this path.
Honesty about the trust trade: hosted storage is operator-readable at rest unless the user chooses client-side encryption (keys never leave their devices; the DO then stores opaque objects and hosted search/index features degrade to what metadata allows). The hosted tier sells availability and convenience, not self-hosted confidentiality — the offer must say so in those words, and the encryption option must exist, or "hosted ID" quietly becomes "trust us with everything."
Shape comparison (per-user cost drivers)
Shape
What it is
Idle cost
Marginal cost drivers
Fit
A. Static site + local storage
Web client only; data on the user's device; P2P sync when peers meet
~zero (asset hosting)
Bandwidth for app assets only
The free floor. No availability guarantee; device loss = data loss unless synced. Every hosted user should be able to fall back to this — the client is the same.
#2044's design: DO per user, hibernating WebSockets, blobs in R2
~zero (DO storage only, GB-months)
Requests / live-session time, stored GB, R2 volume. All meterable per user from the DO itself
The hosted offer. Structural isolation, self-limiting blast radius, exportable (below).
C. Websites-with-backends (apps/worlds)
Per-world/app-instance DOs rather than per-user; shared multi-tenant Worker front
~zero idle
Per-world request/storage costs, attributed to the world (and its owner/sponsor), not to each participant
Complement to B, not an alternative: users are per-user (B); shared things are per-thing (C). A single shared multi-tenant DB for user stores was considered and rejected — it trades away B's structural isolation to save money that B's idle-is-free already saves.
D. Container per user
Always-on small server per user
Non-zero forever
Flat per-user monthly, idle or not
Violates Drew's guardrail — rejected as a tier. Containers remain legitimate only as ephemeral, on-demand jobs (heavy world compute, bulk import/export): started per task, time-boxed, auto-stopped, cost attributed to the job — "a little container use" in the literal sense.
Cost guardrails (making "no indefinite container spend" a system property, not a hope)
Idle = $0 is architectural in shapes A–C (hibernation, no listeners, no per-user processes); anything that would run while a user is away needs explicit justification as a shared, multi-tenant job.
Ephemeral-container rule: any container work names an owner, a max runtime, and an auto-stop at spawn; a container found running without all three is a bug to file, not a pattern to copy.
Exit is a feature: the full store exports as the user's signed objects (self-authenticating by construction) plus the binding record, importable into a self-hosted node or another host. Portability is cheap because the data model is signed objects — and it is the credible answer to "why trust your hosted tier": you don't have to, and leaving is a download, not a negotiation.
Exact free/paid tier numbers and the rebind waiting-period length; whether client-side encryption is default-on for hosted stores (recommendation: offer at binding time with the feature trade explained, default off only because of the search/index cost — flag as a genuine values call for Drew); R2 dedupe vs per-user encryption interaction (encrypted blobs dedupe only per-user — accept it); the Shape C cost-attribution rule for worlds whose owner lapses.
agent-note:v1
state: reviewed (coverage loop-back)
Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 7273 (triage, queued, priority 50) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
Found via: coverage loop-back 2026-11-03
The investigation itself was delivered on this issue on 2026-10-29 (queue task 3354, lane C): verdict (per-user Durable Object store + Google SSO, no per-user containers), provisioning/isolation design, SSO→ID binding analysis, a four-shape comparison (static/local-storage, per-user DO, per-world DO, container-per-user — the last rejected as violating the container-spend guardrail), cost guardrails, and the open questions routed onward.
Activity
developing-today commentedon Oct 29, 2026
CODE-966
dezren39 commentedon Oct 29, 2026
Parent epic: #2056 — ID platform expansion (2026-10-28 directive).
dezren39 commentedon Oct 29, 2026
Top priority — ID program wave-1. Drew directed this be picked up in the next runs (2026-10-29): enqueued directly as triage task 3354 in repo-health-code-pilot at queue priority 20 (only breakage-class work at priority 10 outranks it). Wave-1 is the ungated investigations and designs; syntheses and PoCs stay gated on their named inputs. Investigation route: Workhorse.
dezren39 commentedon Oct 29, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 29, 2026
Investigation result — hosted data option (#2045, queue task 3354)
Verdict
The hosted offer is viable in exactly the shape Drew sketched — per-user store, Google SSO sign-in, no per-user containers — and the #2044 Workers-fit investigation already settled the platform half: one Durable Object per user (SQLite-backed, strongly consistent, serialized per user, ~1,000 req/s soft cap per object, 10 GB per object on Paid) is the per-user server + database, costs nothing while idle, and the hosted edition is necessarily an HTTP/WebSocket-shaped subset because Workers have no UDP for Iroh transport. This investigation covers what #2044 did not: provisioning and isolation, the SSO→ID binding (the subtle part), the alternative shapes, and the cost guardrails.
Provisioning and isolation
idFromNameover the binding ID); the DO and its SQLite schema materialize on first use. No sign-up-time fleet of anything; an account that never returns costs one small account row.WHERE user = ?leaks across users. Cross-user interaction never touches another user's store directly — it moves signed graph objects through the platform's normal exchange surfaces (shared worlds, messages, public stores), where the receiving store applies its own ingest rules (id: investigate: trust/visibility model — servers accept all signed objects; views count only allowed writers #2030: validity at ingest, counting at view). A hosted user and a self-hosted peer are indistinguishable at the object layer.SSO → ID binding (the part that must not be hand-waved)
Google identity and Iroh ID are different kinds of thing — one is an authentication relationship with Google, the other is a self-sovereign key — and the design keeps them in separate lanes:
sub(the stable subject identifier), never the email address (emails change, get recycled, and are shared in wayssubis not). Email is stored, if at all, as contact metadata.{google sub, Iroh ID, bound at}countersigned by the Iroh key and attested by the service. Sign-in alone never creates a binding; a Google account with no key proof is an empty hosted account that can claim nothing.groupslayer), exactly as in the self-hosted model.Shape comparison (per-user cost drivers)
Cost guardrails (making "no indefinite container spend" a system property, not a hope)
Open for the synthesis (#2046)
Exact free/paid tier numbers and the rebind waiting-period length; whether client-side encryption is default-on for hosted stores (recommendation: offer at binding time with the feature trade explained, default off only because of the search/index cost — flag as a genuine values call for Drew); R2 dedupe vs per-user encryption interaction (encrypted blobs dedupe only per-user — accept it); the Shape C cost-attribution rule for worlds whose owner lapses.
Follow-on routing (same pass)
🤖 Agent note · done — queue worker (repo-worker-code), lane C, task 3354. Investigation recorded here; spec follow-on filed + enqueued; #2046 inputs complete.
3 remaining items
developing-today commentedon Nov 3, 2026
agent-note:v1
state: reviewed (coverage loop-back)
Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 7273 (triage, queued, priority 50) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
Found via: coverage loop-back 2026-11-03
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 3, 2026
🤖 Agent note · done
already_done.