Files
trail-mate/docs/geocaching-client-runtime.md

291 lines
18 KiB
Markdown

# Geocaching client runtime
## Current index generations
The shard-head codec and readers support immutable current-only generations.
The existing 52-byte shard head uses byte 46 as a CRC-covered format flag:
`0` selects the legacy append prefix; `1` selects a current-only generation at
`<bucket>.gci.c<sequence as 16 lowercase hex digits>`. Byte 47 remains reserved.
Entries retain the existing 152-byte encoding and verified journal/checkpoint
locators. A current generation contains each live key once, in nondecreasing
record-sequence order, without tombstones. Its head sequence identifies the
generation and can exceed the sequence of its retained records after deletion.
Readers select the head visible to their committed root, read only that generation,
and do not consult obsolete append entries. Empty and shrinking generations are
valid. A nonempty generation must have exactly the length in its head; malformed
references and CRC failures are still rejected. Legacy heads and readers retain
their existing checks.
The production transaction writer maintains current-only object, cache-head,
and draft shards (tables 1, 2, and 4). It folds all mutations for the same bucket in a transaction into one
replacement, verifies the flushed file by read-back CRC, then publishes the
shard head through the existing root commit. Failed verification leaves the
parent head unchanged and requires journal recovery. The bounded writer and its
optional legacy lookup are allocated in PSRAM only; neither retains a complete
shard or GPX payload.
Checkpoint import and rotation preserve this format for tables 1, 2, and 4.
They write checkpoint references to the sequence-named generation before
publishing its head. The checkpoint reader enforces strict key order across
pages; the appender checks the previous entry to maintain that order within each
bucket, rather than searching the whole bucket for every imported row. Journal
appends retain their existing replay behavior. Reference checks, checkpoint
digest validation, and replacement equivalence still precede root publication.
Updating a legacy object, cache-head, or draft bucket converts that bucket during
the write. The existing recovery audit also detects untouched legacy buckets in
these tables. After a writable operation completes, idle maintenance uses the
existing checkpoint rotation to convert them without waiting for the ordinary
256-record interval. Foreground work takes priority; a pinned checkpoint slot
defers another attempt until the root changes. No separate migration scan or
service is created. Current-format local lists remain read-only. Downloaded
detects legacy cache-head buckets or missing object display fields and requests
one bounded, transactional compatibility upgrade before retrying that page.
The remaining tables retain append shards. Exclusive index-slot
cleanup recognizes generation filenames; older generations remain until their
retired index slot is cleaned.
Local lists and the standalone Map can open a CRC-checked committed index root
before journal inventory or suffix replay. The read lease is not a write-ready
snapshot: saving, downloading and publishing still require full recovery and
reference validation. An interrupted operation that has not reached the index
root may therefore remain absent from local views until recovery runs. The
existing repair floor and archive checks still apply, and an unreadable referenced
record triggers recovery rather than being silently accepted.
A read-only Downloaded session does not create the draft/publication storage
adapter. The storage worker creates it when the draft list or draft map section
is requested, or when full recovery prepares a write-capable session. Network
query and dispatch services retain their separate on-demand activation.
Old saved objects without display metadata are upgraded once, using their
retained signed response and committed Installed record. The upgrade admits
writes through full suffix recovery and reference validation, authenticates the
exact cache ID and revision, preserves retention fields, and rewrites only its
object projection and unchanged cache head. It does not open GPX files, change
installation generations or cleanup proofs, or start network services. Failure
leaves the old data intact and reports an error instead of retrying forever.
Subsequent Downloaded pages read only current heads and object projections;
full details still verify the retained signed record. An operation-scoped volume
session avoids repeated format reads while checking media/USB transitions on
every read; CRC and reference checks are unchanged.
The native runtime regressions simulate 25 ms per SD I/O and 38,904 bytes of
internal free heap with PSRAM available. The oldest saved-object upgrade finishes
in 9,660 simulated milliseconds; reopening its Downloaded page takes 1,225 ms.
The tests also cover already projected legacy buckets, an object-only legacy
bucket, and a corrupted author signature. They assert unchanged edited GPX,
unchanged installation/cleanup metadata, no GPX reads during upgrade, no history
reads or writes after reopening, offline full details, and released PSRAM.
These timings are regression evidence, not hardware measurements.
## Archive a published cache
Foreground publication and download dispatch use their already persisted
48-byte request key. The dispatcher reserves and reloads that request directly
instead of selecting it through unrelated attempt-expiration and task-history
scans. Durable intent, cancellation and current identity checks still run before
transport submission. A request already submitted is not reserved again while
its client waits for the directory response. The reply budget starts after
transport submission; durable preparation has a separate bounded wait. First
responses matching current work go directly to validation and result commit.
Historical receipt lookup is reserved for old or duplicate requests.
LXMF resource advertisements use MessagePack text field names, matching Python
Reticulum. Binary field names cause the reference parser to reject the
advertisement and close the link before receiving the publication body. Native
wire vectors and pinned Python decoder checks cover this interoperability rule.
Open the cache in **My caches**, save any edits, and choose **Archive cache**.
The device first saves an archived draft, then asks for confirmation before
publishing the next signed revision with the original author identity.
The local copy remains available. Saving the draft alone does not withdraw
the public cache: the list shows **Archive not confirmed** until the directory
accepts that revision. Reopen it and choose **Confirm archive** to retry an interrupted
publication. **Delete local cache** only removes this device's copy.
The standalone Map excludes an archived local draft as soon as the archived
state is saved, including while directory confirmation is pending. The record
remains in **My caches** so publication can be retried or its local copy deleted.
Archival is a terminal record state. A directory receiving the signed revision
can exclude it from active results; it does not erase signed history or copies
held by other devices and independent directories.
Publication event logs distinguish transport submission from a persisted
directory acknowledgement:
```text
[Geocaching][Publication] submitted cache_prefix=15058ac4 revision=3 state=2
[Geocaching][Publication] confirmed cache_prefix=15058ac4 revision=3 state=2
```
`cache_prefix` is the first four bytes of the cache ID, not its name or complete
ID. State `2` is Archived. Only `confirmed` proves that the matching directory
response was accepted and its result committed; `submitted` and generic Wi-Fi
TX success alone do not prove archival. These are event logs, not periodic polls.
Device discovery and browsing use a disposable RAM session. Opening Discover,
checking directory capabilities, querying, refreshing and paging do not open SD
storage or recover previous queries. A response is displayed after the existing
transport authentication and query validation; no journal commit is required.
`LiveQueryPort` owns one request, one page and two response fingerprints. Its
object size is capped at 3 KiB, excluding the query client and transport stack.
An accepted transport submission retries the same request after 15 seconds;
a rejected submission retries after one second. The query client's existing
120-second deadline cancels further sends. Closing abandons the RAM request.
Exact accepted duplicates can be acknowledged without disk access.
Opening an item requests its exact signed revision and shows the full description
and hint after author verification. Only the open detail owns these text buffers;
closing it cancels further requests. Online detail reads do not require SD.
Saved items read the complete signed record already retained by the download
transaction, so descriptions and hints remain available offline after restart.
Saving an open verified online detail copies its immutable response into a
separate PSRAM allocation for installation. The detail retains its own response,
so staying on the page or closing it cannot invalidate the other owner. The
installation reparses the copy to bind borrowed fields to its own bytes and
reuses the verified identity and hash. It does not repeat the network request
or Ed25519 verification, or allocate a second verification workspace.
The handoff still checks the source, request ID, revision hash, summary fields,
and that the verified signature borrows the retained response. New network
responses continue through full signature verification.
The GPX also includes both fields and the signed record; no second sidecar file
or background detail synchronization is introduced.
Storage starts when the user opens Downloaded, Published or the standalone Map,
or saves a cache. The standalone Map also displays local drafts with a selected
position, regardless of publication status, alongside downloaded caches. It
requests four metadata rows per asynchronous window, retains at most 32 markers
nearest the map center, and stops reading once that pass completes. Panning or
zooming selects a new nearby set. The shared map overlay capacity also includes
other layers, so dense views can show fewer than 32 geocaches. Drafts without a
selected position are excluded; an explicitly selected (0, 0) is valid. No GPX
parse or network discovery is performed by the map projection.
Downloaded rows may be displayed while unfinished downloads are being recovered;
creation and writes still wait for that recovery to finish. Draft pages read four
rows and one lookahead, expose committed names and positions before publication
history is resolved, and skip publication history entirely for unbound drafts.
Publication preparation still requires the complete history projection. Startup
validates request/task/attempt tables in the reference pass rather than scanning
the same tables a second time in the preceding integrity pass.
The draft editor uses the shared map location-selection route instead of latitude
and longitude text inputs. Confirming writes WGS84 E7 coordinates; cancellation
preserves the original position and all live editor fields. Existing coordinates
seed the map viewport, and the selected position is displayed read-only.
Reticulum starts when Discover is requested or publication readiness is checked.
Opening saved lists, saved details or the local draft list does not create a
network backend. Once started, the backend remains available to that session
and its pending network operations; closing the session releases its ownership.
Downloads and publications retain signature checks, durable transaction state,
verified GPX installation, restart recovery and durable reply receipts. Storage
failure or USB ownership does not prevent Discover from receiving a page.
Checkpoint rotation shares storage ownership with persistent operations but
does not delay network queries or pagination.
Installed downloads have a separate durable completion marker for file cleanup.
The optional seventh GpxInstall field is integer `1`, accepted only for the
Installed phase. Older six-field records remain eligible for one recovery pass.
The marker is committed only after target verification and successful history
retention; interrupted cleanup remains recoverable. Recovery also fills missing
list metadata from the retained signed record. Subsequent startup skips these
completed installations without reopening their GPX files. Downloaded pages use
the current head/object/install indexes, with four rows and one lookahead, rather
than scanning request history or hashing GPX files.
Local list row tokens combine the current catalogue's lifetime and its own
content generation. Download and transport progress do not change Published
row tokens; publication progress does not change Downloaded row tokens. Replacing
a catalogue gives it a new lifetime token, so stale selections from a closed
session cannot become valid when a new catalogue starts at generation one.
Status messages and action buttons update independently of row rendering.
Discover rows use the committed query page's content generation. Query phase,
failure text, detail responses and unrelated publication progress do not change
that version. A separate source-lifetime token and saved-membership generation
invalidate selections when the browser session is replaced or Downloaded
membership changes. Pagination retains the existing rows while requesting the
next page; committing the replacement page invalidates the previous selection.
Background publication recovery filters completed, cancelled and no-intent
requests before opening their parent tasks. Exact cache lookups still read
confirmed receipts, and eligible unfinished requests retain task/reference
validation. Selecting unfinished work scans the current request index; it does
not reopen every completed task or GPX file.
Legacy kind-3 browse tasks remain readable on existing cards. The indexed
dispatcher skips them without a startup scan that rewrites each task. Older
query storage adapters are retained for storage-format compatibility tests;
the device runtime no longer instantiates them.
Host verification uses the production runtime with simulated transport, SD and
clock: absent SD, USB ownership, busy sends, bounded retries, exact duplicate
checks, pagination during maintenance, saved downloads, publication updates,
index corruption, interrupted recovery and checkpoint reclamation. These checks
do not establish ESP peak heap usage or real network latency.
## Incremental commit validation
New task creation validates its matching task/request pair and requires both
keys absent before writing. Beginning a send attempt validates the existing
task, request, intent, and installation generation before changing lifecycle
state. These two owners preserve existing reference links and validate only
the changed rows. Attempt-only updates similarly check their outgoing target.
Deletion, rebinding, and recovery retain full reference validation.
Read faults can occur during journal readback after redundant preflight reads
are removed. Fault coverage requires unchanged committed data and rejection of
further writes until recovery; an unconfirmed journal may remain on storage.
## Public withdrawal and local removal
On a verified cache detail, `Archive public cache` is available only when the
record author matches the current device identity. Confirming creates a signed
successor with the same cache ID, the selected revision hash as its parent, and
state `2`. An existing authored draft is retained with the archived state.
The UI remains `Awaiting directory confirmation...` until the matching accepted
directory result is committed. An offline delete never substitutes for this
public operation. The same action is available on an owned Downloaded detail.
Directories hide archived heads from ordinary queries, including continuation
pages created before archival. They reject ordinary reads of a former active
revision with `410 cache_archived`, including retries of previously successful
GET requests. Explicit signed tombstone reads and registered directory peers
retain access for replication. Publication receipts and synchronization history
remain durable; already downloaded copies cannot be remotely erased.
`Delete local copy` only clears the selected installed head and advances its
installation generation in a journaled transaction. This removes the offline
entry from Downloaded and Map and prevents older installation tasks from
restoring it. Immutable historical objects and GPX files remain on SD; this is
not a promise of immediate physical file reclamation. Local map projections
invalidate on committed saves, removals and installation changes even when the
viewport stays unchanged. Work and payload buffers are allocated in PSRAM.
A later download may replace the retained GPX only after its bytes match the
last committed installation for that cache from an earlier generation. A
modified or unowned GPX is still rejected. Removing the head does not reactivate
old download tasks. When identical history already exists, installation
reacquires the completed receipt before checking the retained file and removing
its duplicate backup; that cleanup is also safe to resume after restart.
Archive retries reuse an existing author reservation's issuance time and verify
the reconstructed revision hash before signing. Advancing the wall clock must
not create different bytes for a previously reserved revision. Preparation
failures are reported in the detail action and once in the publication log.
Object references (table 1), installed heads (table 2), and local drafts (table
4) use current-reference shard generations. Repeated changes to one key retain
one lookup reference, with older generations still available to pinned roots.
Legacy shards convert when next written; untouched legacy buckets are detected
by writable recovery and converted by idle checkpoint maintenance. A card used
only for local draft reads retains its compatible legacy reader until a write
occurs. Legacy Downloaded records use the one-time metadata upgrade above.
Downloaded page reads allocate index buffers only. Signed-record verification
and the 8 KiB response buffer are allocated when a detail, download, or legacy
metadata upgrade acquires its lease, rather than during current-format startup.
All these buffers remain PSRAM-only and are trimmed after the lease is released.