8.6 KiB
Item Handles & Physical Labels (Design in Progress)
Status: not implemented. This document collects the problem, goals, and open design questions for giving inventory items stable identifiers and physical (scannable) labels. Nothing here is settled.
Problem
As described in federation.md's "Unique Handles" section, an item today is identified only by a local id scoped to its owner, it isn't given an explicit, portable handle the way a tag, property, or category is. That's fine as long as the only thing ever addressing an item is the owning user's own signed API traffic. It stops being fine the moment something outside that loop needs to refer to the item:
- A friend who borrowed a physical tool has no way to look it up other than finding it in the owner's shared inventory list by eye. There's nothing you could put on a sticker.
- If items ever need to be referenced from outside their owner's own requests (a group's shared view, a lending record, a printed label), there's currently no stable identifier to reference that's meaningful outside the owner's own account.
- A local database id isn't something we'd want to expose or rely on externally: it's an implementation detail of one backend's storage, not a handle with the same guarantees (uniqueness, meaning, longevity) the rest of the federation model gives every other kind of entity.
Put simply: every other kind of thing in Toolshed (users, tags, properties, categories) has a handle that means something outside of one database. Items don't, and physical labeling is the clearest case where that gap actually matters.
Goals
- Give an item a handle that's meaningful and resolvable outside its owner's own account, without requiring items to become shared/reusable entities the way tags are (an item is still owned by exactly one person; see the "Items" subsection of federation.md for why that keeps things simple).
- Support a physical label (QR code, barcode, or similar) that can be printed and stuck on a real object, such that scanning it gets you to the right item on the right backend.
- Make the label survive the normal life of a physical object: it gets lent out, comes back, maybe changes which storage location it lives in, all without needing a new label printed.
Non-goals (for now)
- Turning items into shareable/reusable entities across owners (that's what tags/categories are for; see tags.md). An item handle identifies this specific person's specific thing, not a class of thing.
- Solving inventory tracking/auditing (check-in/check-out logs) as a whole system; that can build on top of a stable item handle once one exists, but isn't the same problem.
Open design questions
What does the handle look like? The natural extension of the existing scheme is owner handle + local id, that's enough to be globally unique (no two users share a handle, and ids are already unique within one user's inventory) without inventing a new namespace. Worth deciding whether the id should be the existing internal database id (simple, but leaks a little implementation detail and a rough count of someone's inventory) or a separate opaque id generated for exactly this purpose (see the unguessability question below).
Two distinct formats are needed, because "an item handle" is used in two different situations:
-
A compact handle, for use where context already makes clear it's a Toolshed item. Inside the app, in exports, in logs, anywhere the reader already knows they're looking at Toolshed data, the handle doesn't need to spell that out or be openable on its own. This can be as short as
user@domain.tld:id, the item's owner handle with:idappended, mirroring how a tag/category handle already appends:nameafter its origin (see federation.md's Unique Handles section). No new delimiter concept, just the same pattern applied to items. -
A self-contained URL, for use with no context at all. A physical label, a link shared outside the app, has to work without the reader already knowing what it is or which server it belongs to, so it needs to open directly to the right frontend, resolve the right backend, and land on the right item. That means it has to encode the same information (owner handle + item id) as a full URL, e.g.
https://toolshed.webdomain.tld/i/alice@example.com/42, note the owner's handle can be embedded in a path segment as-is (@doesn't need escaping in a URL path), which keeps it one segment shorter than splitting the handle back intodomain/user, and means the handle is visible unmodified inside the link rather than reassembled from separate parts. If the owner is ever a group rather than a user (see groups.md), its handle carries a leading#, which does need the+-for-#substitution described in handles-and-shortids.md's Handle syntax section before it can sit in a path segment, e.g.https://toolshed.webdomain.tld/i/+climbing@example.com/42. The frontend host in this URL (toolshed.webdomain.tld) doesn't have to be, and generally won't be, the backend authoritative forexample.com, any frontend can resolve any handle (see federation.md's Servers subsection), so this is just whichever frontend happens to be handling the link, not part of the item's identity.
Both formats should stay as short as the encoded information allows, this matters most for the URL form, since it's the one that ends up in a QR code or printed label where physical size is a real constraint (see the labels goal above).
How does this fit with the frontend's existing routes?
There's already a /inventory/shared/:user/:id route (InventoryDetailForeign), but today
:user is just a bare username with no domain, i.e. it only works for a friend on the viewer's own
domain, and the view itself doesn't yet do anything domain-aware with that param. A resolvable
global handle needs the full user@domain.tld and a lookup step this route doesn't have yet. Two
ways to reconcile that: extend the existing route to take a full handle in the :user segment
(/inventory/shared/alice@example.com/42, no new route shape needed, just a richer meaning for the
param it already has), or treat the short /i/... URL as a dedicated, minimal entry point whose
only job is to resolve a handle and then hand off into whatever the richer in-app view ends up
being. The two aren't mutually exclusive: the short form is what needs to be small enough to print,
the in-app route doesn't have the same constraint and can stay more descriptive.
What does scanning a label actually do? Probably: the label encodes a URL or handle-like string; scanning it opens the frontend, which resolves the owner's domain the same way it resolves any other handle (see federation.md), and lands on that item. This reuses the discovery mechanism that already exists for logging in as a handle, rather than inventing a second one.
Does resolving a label require authorization? An item's availability policy already controls who can see it (owner-only if private, friends if shared, etc.). A label should presumably respect the same policy rather than being a backdoor that makes a private item visible to literally anyone who finds the physical object and scans its code. That means resolving a label isn't a free public lookup, it goes through the same friend/signature checks as everything else, which has UX implications (an anonymous finder of a lost tool can't necessarily see who it belongs to).
Does the label need to be opaque/unguessable? If item ids are small sequential integers, a label built from a guessable id lets anyone enumerate a user's items by scanning or guessing nearby numbers, even if each individual lookup is authorization-checked. Probably wants some amount of unguessability even before authorization is considered, as a defense-in-depth measure.
What survives item changes? Storage location, availability policy, name, and description can all change over the life of an object without it becoming a "different" item. The label should point at the handle, not at any of that mutable data, so none of those changes require a new label. The one thing that probably does need a decision is deletion: does a handle ever get reused, or is it retired for good once an item is deleted (retiring seems safer, avoids an old label resolving to an unrelated new item later)?
Relationship to lending/borrowing. A scannable label is the obvious hook for a future "mark as borrowed / returned" flow. Not solving that now, but the handle scheme chosen here should be able to carry that later without a redesign, i.e. it should be able to identify the item independent of who currently physically has it.