Records, folders, presentation and sharing
OakGate uses the same Acorn records and temporary-copy protocols as Safebox Web.
The custody model changes who retains the key, not the underlying record format.
All user-key operations remain in Acorn. Public OpenETR evidence is retrieved
and signature-checked through the explicit stroma-nostr dependency.
User experience
Scan and Receive are stacked full-width actions beneath navigation. Add Record appears only on the Records page for connected Acorns. All three use a minimum 56-pixel target height for access from either side of the screen.
-
Add Record creates a labelled text or JSON record with an optional attachment. Labels are limited to 200 characters, text to 256 KiB and files to 25 MiB. Under the OakGate wallet lease, Acorn checks whether the label already exists; an existing label is rejected rather than overwritten. Acorn performs publication and verification. An uncertain save directs the user to check Records before retrying. This check does not provide atomic exclusion against another independent client writing to the same Acorn at the same time.
-
List shows records newest first. Folders groups existing labels by
/, with breadcrumbs. Folder names sort alphabetically; records retain timestamp order. These are label groups, not a separate folder database. - View retains the existing image, PDF, pass, credential and mdoc previews.
- Edit updates text or JSON, preserving the label and record type. An optional file replaces the attachment; leaving it blank preserves the existing file. Text is limited to 256 KiB and files to 25 MiB. Acorn must confirm the published record before OakGate reports success. Reload after an uncertain save.
- Delete opens a separate confirmation page showing the record label, a
required confirmation checkbox and Cancel. Only a CSRF-protected POST can
delete. OakGate takes the wallet lease, rechecks the record reference against
the user's Acorn and calls
Acorn.delete_recordfor kind 37375. Success requires Acorn to report that the configured relay no longer returns the record. An uncertain outcome asks the user to reload Records before retrying. NIP-09 is advisory: historical relay copies may remain. Separately stored attachments and sharing copies are retained; this action does not claim complete erasure. - Present creates a view-only presentation code for a compatible Safebox.
- Share creates a code that a compatible Safebox can import.
- Check computes the original file's SHA-256 locally and queries configured OpenETR relays for anchor events (1415) and publisher notices (1416). The user confirms disclosure of the fingerprint first. Neither file contents nor a private key are sent in the evidence query.
Present and Share copy one record and its attached file. New codes expire after two minutes in compatible clients. They are bearer secrets: anyone holding one can read the copy. Presentation's view-only convention cannot prevent copying, and neither expiry nor a successful deletion request proves removal from all storage.
Active shares retains the codes and Stop control across navigation, sign-out and app restarts. Stopping calls Acorn's transfer-scoped deletion method; OakGate removes its saved descriptor only after deletion is confirmed. It cannot recall copies already received. An upload followed by a lost response or database failure can leave an untracked copy; OakGate reports the outcome as uncertain and does not automatically retry. The active Present/Share page shows a two-minute progress bar and countdown, then submits the normal CSRF-protected Stop request through Acorn. Reloading a new code does not extend its embedded expiry. Browser suspension can delay that deletion request; expiry still applies in compatible clients. Closing the page does not schedule background deletion. Unconfirmed deletion retains the code for manual retry through Active shares. Older codes retain their original embedded expiry, but an open page requests Stop within two minutes. There is no background expiry/deletion worker yet.
OakGate's Scan action accepts the same Acorn presentation and transfer codes as Safebox Web. Presentations render content and supported attachments without importing them, and show the OpenETR evidence check. Transfers show their type, attachment presence and expiry, with a Store as label before import. A label already in this OakGate wallet is rejected before claiming the import ticket; this is not atomic exclusion against another independent Acorn client. Each ticket remains owner-bound and one-shot, including uncertain results. Import and temporary copy deletion run through Acorn. Record renaming and folder moves are not added.
Check now uses Safebox Web's candidate/signer/publisher-notice presentation and separates Anchor found, No anchor found, and Check unavailable. A local record check still retrieves and hashes its original bytes before querying. Timeouts return an unavailable check, not a message implying a failed save. Matching anchors can show a public OpenETR evidence link and QR. That link contains a fingerprint, not the private record or its sharing secret.
Scanning an exact OAKGATE_OPENETR_PUBLIC_BASE_URL/<sha256> link opens an OakGate
evidence check using configured relays. A scanned fingerprint does not prove
possession or verification of the original bytes. Unrecognized HTTPS links stay
on the external-link review page; they cannot select relays or storage servers.
GS1 Digital Link, GTIN and element-string discovery remain available, alongside
Lightning addresses/invoices, Cashu tokens and Clear payment requests.
Present/Share pages have tap-to-copy QR codes and Copy code buttons, expiry information, progress feedback on Stop, and a best-effort leave-page warning. Expired codes no longer show a usable QR, but remain in Active shares so deletion can be retried. Closing a presentation view does not revoke the sender's copy.
Infrastructure configuration
OAKGATE_BLOSSOM_SERVER=https://grove.safebox.dev
OAKGATE_TRANSFER_SERVER=https://grove.safebox.dev
OAKGATE_OPENETR_RELAYS=wss://relay.openetr.org
OAKGATE_OPENETR_PUBLIC_BASE_URL=https://openetr.org/etr
OAKGATE_OPENETR_QUERY_TIMEOUT_SECONDS=5
OAKGATE_OPENETR_QUERY_LIMIT=100
OAKGATE_OPENETR_BLOSSOM_SERVERS=https://grove.safebox.dev
OAKGATE_RECORD_ALLOWED_SERVERS=https://grove.safebox.dev
The first server receives new/replacement encrypted attachments; the second receives temporary encrypted sharing/presentation copies. Existing attachments continue to resolve through Acorn. Transfers read existing blobs using Acorn's public-only Stroma retrieval. Operators can select community-run infrastructure. OpenETR uses 1–8 comma-separated public WSS relays. Queries are bounded, reject private DNS destinations and redirects, and require no Google or OpenETR account.
Core Record Ruleset 1.0 and its presentation adapter are carried over from
Safebox Web (app/openetr_rules.py, app/openetr.py). OakGate displays candidate
anchors, publisher notices and inspectable findings. Unavailable evidence is not
reported as a clean check. Bounded discovery cannot establish completeness,
recognition, legal validity or ownership. This integration does not publish
OpenETR events or expose a user's private key to the evaluator.
Nginx upload limits
Add this directive inside the existing HTTPS server block for each host:
oakgate.safebox.dev and grove.safebox.dev. Keep their existing TLS and upstream
settings.
client_max_body_size 110M;
The upload passes through two proxies: browser → OakGate, then Acorn → Grove for encrypted storage. Temporary sharing copies also use the configured transfer server. A limit at either proxy can cause 413 Request Entity Too Large. A successful browser preview does not test either upload: the preview is local. In the deployed setup, raising this Nginx limit resolved attachment-save failures.
This is the proxy request-body limit, not the application file allowance. OakGate continues to limit attachments to 25 MiB, form requests to 26 MiB, and record text to 256 KiB. Grove may enforce its own application limits. The larger proxy allowance provides room for request and encryption overhead; it does not enable 110 MiB attachments in OakGate.
After editing the Nginx configuration, validate and reload on each proxy host:
sudo nginx -t && sudo systemctl reload nginx
For container-managed Nginx, run the equivalent validation and reload in that Nginx container. Rebuilding OakGate or restarting Keycloak is not necessary for an Nginx-only change.
If a 413 persists, inspect the effective configuration with sudo nginx -T:
check both hostnames and any matching upload location blocks for a smaller
client_max_body_size override. Check the access/error logs on both proxies to
identify which rejected the upload. Check Records before retrying an uncertain
save, then verify that the saved attachment opens correctly.
Storage and backups
Records remain in the Acorn, not a new OakGate content database. Temporary bearer
codes are encrypted with the operator custody key in the record_capabilities
table of the existing custody.sqlite3. Each descriptor is bound to both its
owner's issuer/subject identity and an opaque capability ID. Backup the consistent
SQLite database and the custody key as described in the identity/storage note.
The server-side owner binding prevents one user from viewing or stopping another
user's sharing copy. Redis remains session storage only.
Acorn dependency and deployment order
This feature includes changes in the sibling safebox-acorn repository:
- Prevent uncertain saves from deleting existing or newly uploaded attachments.
- Run synchronous Blossom upload/deletion calls off the application event loop.
- Add
public_onlyto record transfer/presentation creation. - Advertise these guarantees with
Acorn.record_workflows_version = 1.
Publish those Acorn changes to main first. Then, in OakGate:
poetry update safebox-acorn
poetry run python -c 'from acorn.acorn import Acorn; assert Acorn.record_workflows_version >= 1'
poetry run pytest
Commit the updated lockfile with OakGate. Its dependency declaration stays on
main; the lockfile must resolve to the published changes for a reproducible
container build. Until then OakGate blocks Edit, Present and Share instead of
using an unsafe older implementation. Source-override development tests do not
establish that the installed or container dependency has been updated.
On the deployed machine, use ./refresh-app.sh; Keycloak does not need a restart.
Before testing against valuable records, exercise editing an attached disposable
record and presenting/sharing it with another Safebox, then confirm Stop behavior
against the selected Blossom service.
Record transfers and address discovery
Sharing and presentation use Acorn-generated transfer descriptors carried in
QR codes or links. They do not resolve a recipient through NIP-05 or the wallet
directory. Future address-based record delivery must use explicit wallet
capabilities in safebox.json, not a social key. See the
resolution contract
for how this differs from payments and wallet messages.
Deployment and smoke test
These settings are passed into the app by Compose. Update .env, then rebuild
and recreate only OakGate with ./refresh-app.sh; Keycloak and Redis need no
restart. This change uses the existing Acorn presentation/transfer APIs and does
not require a new Acorn commit or any change to Safebox Web.
Public OpenETR artifacts may live on a different Grove/Blossom server from private
wallet attachments. List those public sources in OAKGATE_OPENETR_BLOSSOM_SERVERS.
For cross-application Present/Share, both applications must trust the server that
hosts the temporary encrypted copy. Add approved remote servers to
OAKGATE_RECORD_ALLOWED_SERVERS; OakGate's own Blossom and transfer servers are
always included. Merely scanning a code never authorizes a new server. Limits:
1–8 servers per list, 1–15 seconds per query, 1–200 events per query.
After deployment, use two test Acorns with a harmless record and attachment:
- Open Check; compare the file fingerprint and candidate/signer evidence with Safebox Web. Test an unanchored file as well as a known OpenETR record.
- Present in each application and scan in the other. Confirm the preview opens without adding a record to the receiver, then stop the presentation.
- Share in each direction; choose a new destination label and import once. Confirm the record and file appear in the receiver's Acorn. Resubmitting the same ticket must not import again.
- Copy/scan an OpenETR evidence link and a GS1 code with a known published anchor. Compare artifact retrieval and candidate selection against Safebox Web.
- Repeat on a phone; test clipboard fallback, expiration and unavailable relays.
Real camera permission, deployed relay contents and cross-instance server access require this deployment smoke test; isolated regression tests cannot prove them.
Originator handle suggestions
New shares and presentations include the sender's current OakGate handle as
optional metadata inside Acorn's encrypted transfer envelope. The QR descriptor
still carries only the bearer capability; the handle is read after decryption.
On acceptance, OakGate suggests handle/existing label in Store as, which the
recipient may edit. Existing labels remain protected against overwrites. If the
combined label exceeds the 200-character limit, the original label is suggested.
The handle is a display hint, not verified authorship or a social identity claim.
Publish the optional originator_handle Acorn API change first, then run
poetry update safebox-acorn in OakGate and commit its lockfile before deployment.
Old envelopes decode without a handle; older Acorn readers ignore the extra
optional field. Safebox Web's existing calls and default import labels are unchanged.
Sharing fingerprints and recipient labels
QR and NFC receipt labels use a fingerprint of the originating Acorn key, record content and receiving Acorn key. Repeated unchanged shares to the same wallet suggest the same label; changed content or a different wallet changes the sharing fingerprint. Recipient edits preserve the accepted label, and the original-file fingerprint remains separate for verification.
See Record paths and fingerprints for the precise contract, collision handling, text-only notes, prescription and ticket examples, and deployment requirements.
Paginated browsing and direct reads
The wallet Records page uses Acorn's get_user_records_page against the wallet's
bootstrap relay. Relay batches request up to 100 events, but browser pages contain
up to 20 visible records, assembled across batches after internal-record and
selected-folder filtering. Next page appears only after finding another visible
record. Overflow metadata is retained in the encrypted continuation state so no
records are dropped when a relay batch crosses a browser-page boundary.
Searches are bounded to 100 relay batches and 40 seconds per browser request. Lookup failures or exhausted search budgets are reported as unavailable, not an empty wallet. First page refreshes discovery from the start. Folders are grouped from the current page's records, so a page may show fewer than 20 distinct entries. Opening a folder collects matching metadata across relay batches and sorts paths alphabetically (case-insensitive) before pagination. This avoids alphabetical order restarting with each newest-first relay batch. Remaining metadata stays in the encrypted continuation state; attachments are not fetched. The same search bounds apply, so very large folders may require future indexing work. The regular List view remains newest-first. Pagination is not a snapshot when records change during browsing.
Continuation links contain a random token. The underlying cursor and buffered label/reference metadata are encrypted with the instance custody cipher in the session store, expires after 15 minutes, and is bound to the owner, Acorn public key, relay, view and selected folder. An expired or mismatched link must be reopened from Records.
New detail links use the event ID and Acorn's get_user_record, which verifies
ownership and preserves internal-record protections. OakGate never uses an
internal-capable record reader. List responses contain labels, timestamps and
references only; attachments are fetched on demand. Existing hashed detail links
remain supported by a paginated compatibility search and safe direct re-read.
That search is bounded to 200 pages and the service's 40-second read timeout;
failures remain unavailable rather than being reported as an empty wallet.
This change covers wallet list and detail views. Existing bounded discovery used by POS selection and other sharing workflows is unchanged. It requires the updated safebox-acorn dependency and an app rebuild; no database migration or payment recovery changes are involved.
Deferred work and the checklist for resuming it are recorded in the Record management roadmap.