DatabaseTicket: URL-based Shareable Database Links
Overview
A DatabaseTicket encodes a database ID and optional peer address hints into a
compact, shareable URL. Peers exchange a single link to initiate synchronization
rather than coordinating database IDs and transport addresses separately.
URL Format
Magnet-style URI with query parameters:
eidetica:?db=<database_id>&pr=<transport_address>&pr=<transport_address>
- Scheme:
eidetica db(required): The database ID, passed through as an opaque stringpr(optional, repeatable): A self-describing peer address
Database ID
The database ID is stored as-is in the db query parameter, no encoding or
transformation is applied. The ID is a multibase-encoded
CID (Content Identifier) string (e.g., bafyr4i... for
base32lower). The ticket format does not need to understand the ID’s internal
structure.
Peer Address Hints
Each pr parameter contains a peer address prefixed by its transport
name and a colon. The transport’s native encoding follows the colon:
http:192.168.1.1:8080— HTTP transport (plainhost:port)iroh:endpointABC...— Iroh transport (EndpointTicketformat)
The first : separates the transport name from the transport-specific
address. Each transport uses its own native encoding — for iroh this is
the standard EndpointTicket format (postcard + base32-lower with
endpoint prefix).
Multiple pr parameters are supported, including multiple addresses for the
same transport type (e.g., different network interfaces).
Examples
Database ID only (no hints):
eidetica:?db=bafyr4ihkr4ld3m4gqkjf4reryxsy2s5tkbxprqkow6fin2iiyvreuzzab4
With transport hints:
eidetica:?db=bafyr4i...&pr=iroh:endpoint...&pr=http:192.168.1.1:8080
Future Features
tips Parameter
An optional tips query parameter could reference a database at a specific
state. A database state is defined by its complete set of tips, so tips are
encoded as a single parameter value rather than repeated parameters (unlike
pr, where each address is independent). A count prefix makes truncation
detectable:
tips=<count>:<tip_id>,<tip_id>,...
For example, tips=2:bafyr4i...,bafyr4i.... If the count doesn’t match the
number of parsed tip IDs the parameter is discarded as truncated. A receiver
can use the tips to sync to a known state or verify they reached the expected
point. Not needed for normal use.
A set of tips would fully define the database state.
Implementation
DatabaseTicket struct
pub struct DatabaseTicket {
database_id: ID,
addresses: Vec<Address>,
}
DatabaseTicket reuses the existing Address type from the sync module,
which already bundles a transport type identifier with an address string.
Serialization
The Display implementation uses minimal percent-encoding — only characters
that are structurally significant in query strings (&, =, #, +, %)
are encoded. Colons and other characters pass through verbatim, keeping
tickets human-readable.
Parsing uses url::form_urlencoded::parse() which handles both encoded and
unencoded input, so tickets produced by other implementations with more
aggressive encoding are accepted.
Sync::create_ticket
Generates a ticket for a database using all running transports:
pub async fn create_ticket(&self, database_id: &ID) -> Result<DatabaseTicket>
Collects server addresses from all running transports and bundles them with
the database ID into a DatabaseTicket. Lower-level helper that does not
touch user-level sync preferences.
Session-scoped ticket lookup
A Database opened or created by an AccountSession is the high-level API for making a
tracked database shareable. It deliberately does not combine the process into
one atomic call:
Database::sharewrites the user’s signed durable preference.- Owner reconciliation applies the daemon’s private combined configuration independently.
Database::ticketchecks this user’s setting and, when enabled, returns a point-in-time locator built from the owner’s current addresses.
Database::sync_settings exposes only that user’s settings. It does not expose
or consult combined settings, other users, or runtime telemetry. A caller whose
own setting is disabled receives an error even if another user keeps the daemon
serving the database. An unknown write acknowledgment is safe to retry or read
back because setting the preference is idempotent.
A DatabaseTicket is a locator, not a capability: its database ID and address
hints do not grant database permission. Authorization and bootstrap approval
remain separate.
Joining through a client or daemon
AccountSession::join is the high-level consumer of a ticket. On an embedded instance it
uses the existing request_database_access facility. On a connected instance the
daemon owns transports, while the client retains the user’s private signing key.
Neither a second client sync engine nor daemon custody of that key is needed.
A request proof binds the answering peer, database and advertised tips, so a connected client cannot sign before the daemon selects a route. Prepare returns that route and peer with empty tips, without reading local database state. The client signs, and execution checks session-key possession and the proof, then re-handshakes the selected route before recording it. Tickets remain locators, not access grants. See ticket bootstrap for the wire exchange.
The interface composes existing facilities rather than making them one atomic operation: bootstrap transfer and approval, Account mapping persistence, and service framing have separate failure boundaries. Success returns an opened database after verified transfer, Account bookkeeping and a final requested-permission check. Pending requires an explicit retry and writes no Account state; an error does not imply rollback.
This is not device enrollment or a durable completion job. Later daemon sync uses its device key and needs separate authority or a global grant. The existing held-tree Read gate and native authority semantics remain unchanged; see the recovery limits.
Forward Compatibility
Unknown query parameters are silently ignored during parsing. This allows
future parameters to be added without breaking existing parsers. Malformed
pr values (missing : separator) are also silently skipped.