Untitled Project
SplitClone - Software Requirements Specification
1. Introduction
1.1. Purpose
1.2. Glossary
2. Stakeholders and Users
SC-USR-1
2.1. Primary user persona SC-USR-1 The primary user is an adult iPhone owner who already uses OneDrive (personal
or family plan) and wants to track shared expenses with 2 to ~8 other people
they trust (e.g. flatmates, partner, travel companions).
SC-USR-2
2.2. Group size assumption SC-USR-2 The MVP shall be designed for ledgers containing between 2 and 10 participants.
Larger groups are not a goal and may exhibit degraded sync performance.
3. Functional Requirements
3.1. Ledger Lifecycle
SC-FR-LED-1
3.1.1. Create a new ledger SC-FR-LED-1 The app shall let a user create a new ledger by selecting an empty (or new)
folder inside their connected shared folder provider. The app shall write an
initial ledger metadata file into that folder.
  • SC-FR-LED-3
    3.1.3. Ledger metadata SC-FR-LED-3 A ledger shall expose a single plaintext metadata file in its folder. The
    metadata file shall declare at minimum:
    - The ledger UUID
    - The schema version
    - The creation timestamp (UTC)
    - The encryption marker "encrypted": true (see SC-ARC-ENC-1)
    - The key fingerprint (see SC-ARC-ENC-3)
    The metadata file shall NOT contain the ledger's human-readable name, its
    participant list, label list, or any other field that could leak sensitive
    information about the ledger's contents to anyone who can read the folder
    but does not hold the data key. The ledger name and the participant /
    label state shall instead be reconstructed from the encrypted event log
    (see SC-ARC-LOG-2).
SC-FR-LED-2
3.1.2. Open an existing ledger SC-FR-LED-2 The app shall let a user open an existing ledger by selecting a folder that
already contains SplitClone ledger metadata. The app shall validate the
metadata and refuse to open folders that are not recognised SplitClone ledgers.
  • SC-FR-LED-1
    3.1.1. Create a new ledger SC-FR-LED-1 The app shall let a user create a new ledger by selecting an empty (or new)
    folder inside their connected shared folder provider. The app shall write an
    initial ledger metadata file into that folder.
SC-FR-LED-3
3.1.3. Ledger metadata SC-FR-LED-3 A ledger shall expose a single plaintext metadata file in its folder. The
metadata file shall declare at minimum:
- The ledger UUID
- The schema version
- The creation timestamp (UTC)
- The encryption marker "encrypted": true (see SC-ARC-ENC-1)
- The key fingerprint (see SC-ARC-ENC-3)
The metadata file shall NOT contain the ledger's human-readable name, its
participant list, label list, or any other field that could leak sensitive
information about the ledger's contents to anyone who can read the folder
but does not hold the data key. The ledger name and the participant /
label state shall instead be reconstructed from the encrypted event log
(see SC-ARC-LOG-2).
SC-FR-LED-4
3.1.4. Single active ledger SC-FR-LED-4 The MVP shall support having exactly one active ledger open at a time per
device. Switching ledgers shall be possible but is not a frequent operation.
3.2. Participants
SC-FR-PRT-1
3.2.1. Add a participant SC-FR-PRT-1 Any app user with access to a ledger shall be able to add a new participant
to that ledger by providing a display name. The system shall assign a stable
participant UUID.
SC-FR-PRT-2
3.2.2. Claim a participant identity SC-FR-PRT-2 When a user opens a ledger for the first time on their device, the app shall
let them either (a) claim an existing unclaimed participant entry, (b) create
a new participant and claim it, or (c) re-claim a participant that is already
claimed on another device, when the same person is adding a further device
(e.g. PC + phone). A device shall be bound to exactly one participant per
ledger; a participant may be bound to more than one device. Claiming moves
the claiming device only — other devices already bound to a participant are
unaffected. The claim screen shall present (c) candidates separately from (a)
and make clear that picking one links the new device to the same person
rather than creating a duplicate.
SC-FR-PRT-3
3.2.3. Edit participant display name SC-FR-PRT-3 A user shall be able to rename any participant in the ledger. The UUID shall
remain unchanged so existing expenses are not orphaned.
SC-FR-PRT-4
3.2.4. Participants without devices SC-FR-PRT-4 A ledger shall be able to contain participants who do not run the app (e.g.
a flatmate who is included in splits but does not own an iPhone). Such
participants can be added by any other user and never claim a device.
3.3. Expense Entry
SC-FR-EXP-1
3.3.1. Record an expense SC-FR-EXP-1 A user shall be able to record an expense consisting of:
- Title (free-form string, max 200 chars)
- Amount (decimal with 2 fractional digits, > 0)
- Execution date (calendar date when the expense actually occurred, defaulting to today; user-editable)
- Payer (exactly one participant)
- Participants in the split (one or more participants, defaulting to all)
- Labels (zero or more, see section Labels)
The system shall additionally and automatically assign:
- A stable expense UUID
- An entry timestamp recording the wall-clock instant at which the expense was recorded in the app (not user-editable)
The execution date and the entry timestamp shall be stored as two distinct
fields. Example: ice cream bought on 2026-04-22 but entered in the app on
2026-04-28T15:00:00Z yields execution date 2026-04-22 and entry timestamp
2026-04-28T15:00:00Z.
SC-FR-EXP-2
3.3.2. Currency SC-FR-EXP-2 The MVP shall support a single currency per ledger, chosen at ledger creation
and stored in ledger metadata. Per-expense currency override is out of scope.
SC-FR-EXP-3
3.3.3. Optional note SC-FR-EXP-3 An expense may optionally carry a free-form note (max 2000 chars). The note
shall be displayed but does not participate in any calculation.
SC-FR-EXP-4
3.3.4. Edit an expense SC-FR-EXP-4 Any user shall be able to edit any field of any expense. An edit shall not
overwrite the original record in place; it shall be stored as a new event
that supersedes the previous version of the expense (see SC-ARC-LOG-1).
SC-FR-EXP-5
3.3.5. Delete an expense SC-FR-EXP-5 Any user shall be able to delete any expense. Deletion shall be recorded as a
tombstone event so that other devices converge on the deletion when they sync.
3.4. Labels
SC-FR-LBL-1
3.4.1. Ledger-scoped label list SC-FR-LBL-1 Each ledger shall maintain a shared list of labels. The list is part of the
ledger state and is therefore visible to all users of that ledger. Labels are
not shared across ledgers.
SC-FR-LBL-2
3.4.2. Create a label SC-FR-LBL-2 Any user shall be able to create a new label by providing a display name
(free-form string, max 40 chars, case-insensitive uniqueness within the
ledger). The system shall assign the label a stable UUID.
SC-FR-LBL-3
3.4.3. Rename a label SC-FR-LBL-3 Any user shall be able to rename an existing label. The UUID shall remain
unchanged so that existing expense-to-label assignments are preserved.
SC-FR-LBL-4
3.4.4. Delete a label SC-FR-LBL-4 Any user shall be able to delete an existing label. Deletion shall be
recorded as a tombstone event. Expenses that reference a deleted label
shall continue to exist; the dangling reference shall be silently elided
from the derived state so the label simply no longer appears on those
expenses.
SC-FR-LBL-5
3.4.5. Assign multiple labels to an expense SC-FR-LBL-5 A user shall be able to attach zero or more labels to an expense at creation
time and shall be able to add or remove labels via editing an existing
expense (see SC-FR-EXP-4). Label assignments are stored as part of the
expense record.
SC-FR-LBL-6
3.4.6. Label management UI SC-FR-LBL-6 The app shall expose a dedicated screen for viewing the full list of labels
in the current ledger and for creating, renaming, and deleting labels from
that screen. The same screen shall show, for each label, the number of
expenses currently carrying it.
3.5. Splitting
SC-FR-SPL-1
3.5.1. Equal split (MVP) SC-FR-SPL-1 The MVP shall support equal splits only. Given an expense with amount A and N
participants in the split, each participant's share shall be A/N rounded to
the smallest unit of the ledger currency.
SC-FR-SPL-2
3.5.2. Rounding remainder SC-FR-SPL-2 When A/N does not divide evenly into the smallest currency unit, the rounding
remainder shall be assigned deterministically to the payer so that the sum of
shares always equals the expense amount exactly.
SC-FR-SPL-3
3.5.3. Payer included in split by default SC-FR-SPL-3 By default the payer is included as one of the participants in the split. The
user may exclude the payer at expense entry time, in which case the payer is
fully reimbursed by the other participants.
3.6. Balances and Settlement
SC-FR-BAL-1
3.6.1. Pairwise balances SC-FR-BAL-1 For every ordered pair of participants (A, B), the app shall compute the net
amount A owes B based on all non-deleted expenses and settlements in the
ledger. The computation shall be a pure function of the event log.
SC-FR-BAL-2
3.6.2. Per-user summary SC-FR-BAL-2 The app shall display, for the participant claimed by the current device, a
summary listing for each other participant whether the current participant
owes or is owed and how much.
SC-FR-BAL-3
3.6.3. Record a settlement SC-FR-BAL-3 A user shall be able to record a settlement: a payment from one participant
to another, with an amount and a date. A settlement shall reduce the recorded
balance between the two participants accordingly.
SC-FR-BAL-4
3.6.4. No automatic debt simplification (MVP) SC-FR-BAL-4 The MVP shall present raw pairwise balances. "simplify debts"
re-routing of payments through third parties is out of scope.
3.7. Browsing and History
SC-FR-HIS-1
3.7.1. Expense list SC-FR-HIS-1 The app shall provide a chronological list of all non-deleted expenses,
newest first, showing at minimum: date, title, amount, payer, and split size.
SC-FR-HIS-2
3.7.2. Expense detail view SC-FR-HIS-2 Tapping an expense shall reveal its full detail including all participants in
the split, the amount each owes, the note (if any), the creator, and the
creation timestamp.
SC-FR-HIS-3
3.7.3. Filter by participant SC-FR-HIS-3 The expense list shall be filterable to show only expenses involving a chosen
participant (as payer or as split member).
SC-FR-HIS-4
3.7.4. Filter by label SC-FR-HIS-4 The expense list shall be filterable by one or more labels. When two or more
labels are selected, the list shall show expenses carrying any of the
selected labels (logical OR). The label filter shall be combinable with the
participant filter (logical AND between filter dimensions).
SC-FR-HIS-5
3.7.5. Filter by date range SC-FR-HIS-5 The expense list shall be filterable by an execution-date range (from / to,
either bound optional). The filter shall apply to the execution date
(SC-FR-EXP-1), not the entry timestamp.
3.8. Export to External Finance Apps
SC-FR-EXR-1
3.8.1. Per-participant export SC-FR-EXR-1 The app shall let the user export the ledger as a file representing one
chosen participant's transactions. The export shall reflect only that
participant's individual money movements (see SC-FR-EXR-3), not the raw
shared expense list.
SC-FR-EXR-2
3.8.2. Default participant selection SC-FR-EXR-2 The export screen shall default the participant selector to the participant
claimed by the current device (SC-FR-PRT-2). The user may override this
selection and export on behalf of any participant in the ledger.
SC-FR-EXR-3
3.8.3. Export semantics - two selectable modes SC-FR-EXR-3 The export screen shall offer the user a choice of two export modes for the
selected participant P. Both modes share the same CSV schema (see
SC-FR-EXR-4) and the same sign convention (positive = inflow to P / debit
to P's external account that mirrors the export; negative = outflow /
credit). Deleted expenses and deleted settlements shall be excluded in both
modes.

Mode A - Cash basis:
Contains only real money movements involving P. Intended to be reconciled
against P's real bank or card account in the external finance app.
- Expense E paid by P: one row, amount = -E.amount, date = E.execution date.
- Expense E paid by someone else, P in the split: NOT exported.
- Settlement S where P paid: one row, amount = -S.amount, date = S.date.
- Settlement S where P received: one row, amount = +S.amount, date = S.date.

Mode B - Virtual account (share basis):
Intended to be imported into a dedicated "virtual" sub-account in the
external finance app, where the running balance represents P's net position
inside the shared ledger.
- Expense E in which P is the payer with share s out of total amount T:
one row, amount = +(T - s), date = E.execution date. The row is omitted when T = s (P is the sole participant in the split).
- Expense E paid by someone else, P in the split with share s: one row,
amount = -s, date = E.execution date.
- Expense E paid by someone else, P not in the split: NOT exported.
- Settlement S where P paid amount A: one row, amount = +A, date = S.date.
- Settlement S where P received amount A: one row, amount = -A, date = S.date.

The two modes are mutually exclusive within a single export: each invocation
of the export action produces exactly one file in exactly one mode.
SC-FR-EXR-4
3.8.4. CSV export format SC-FR-EXR-4 The MVP shall produce a UTF-8 encoded CSV file with a single header row and
the following columns, in this order:
1. Date - ISO-8601 calendar date (YYYY-MM-DD)
2. Description - Expense title for expense rows; "Settlement to <name>" or "Settlement from <name>" for settlement rows
3. Amount - Signed decimal with two fractional digits, period decimal separator, no thousands separator. Positive = inflow to selected participant, negative = outflow.
4. Currency - ISO 4217 currency code, taken from the ledger (SC-FR-EXP-2)
5. Counterparty - For expense rows: comma-separated display names of the OTHER participants in the split (i.e. those who owe a share). For settlement rows: the display name of the other participant.
6. Labels - Semicolon-separated list of label display names attached to the source expense; empty for settlement rows.
7. Note - The expense note (SC-FR-EXP-3), with embedded CR/LF characters replaced by single spaces; empty for settlement rows.
8. ExpenseUUID - The stable expense or settlement UUID, so re-exports can be deduplicated by the importing tool.
Field values shall be RFC 4180 quoted (double quotes, doubled internally)
when they contain commas, double quotes, or newlines.
SC-FR-EXR-5
3.8.5. Active filters applied to export SC-FR-EXR-5 The export shall honour any execution-date range filter (SC-FR-HIS-5) and
label filter (SC-FR-HIS-4) that is active in the UI at the moment the export
is initiated, so the user can scope an export to a single month, trip, or
label set.
SC-FR-EXR-6
3.8.6. File delivery in the browser SC-FR-EXR-6 The generated CSV file shall be delivered to the user via a browser
download triggered from a Blob object and an anchor element carrying the
"download" attribute. Where the Web Share API with file support is
available (notably iOS Safari 15+), the app may additionally offer "Share
via..." to route the file into the OS share sheet (Mail, Files,
third-party apps).

The default filename shall follow the pattern
"splitclone_<ledger-slug>_<participant-slug>_<mode>_<YYYYMMDD-HHMMSS>.csv",
where <mode> is "cash" for Mode A and "virtual" for Mode B (see
SC-FR-EXR-3). Including the mode in the filename prevents users from
accidentally importing the wrong projection into the wrong account.
SC-FR-EXR-7
3.8.7. No import in MVP SC-FR-EXR-7 The MVP shall not support importing data from external finance apps, bank
statements (CSV, MT940, CAMT.053), or other SplitClone instances. Data flow
between SplitClone and other tools is one-way (export only) in the MVP.
3.9. Sync
SC-FR-SYN-1
3.9.1. Background sync SC-FR-SYN-1 The app shall pull remote changes from the shared folder whenever it is
opened or brought to the foreground, and additionally at a periodic
interval while it remains in the foreground and connected, so that a
peer's change appears without any manual action. It shall push local
changes to the shared folder within 10 seconds of the user committing
them, network permitting. Periodic polling shall pause while the app is
hidden or offline (the backend offers no push channel, so a bounded poll
is the propagation mechanism; the interval is a tunable constant balancing
freshness against request volume and battery, SRS Q2).
SC-FR-SYN-2
3.9.2. Manual sync SC-FR-SYN-2 The app shall expose a manual "sync now" control that forces an immediate
pull and push cycle.
SC-FR-SYN-3
3.9.3. Sync status indication SC-FR-SYN-3 The app shall display a clear indicator of the current sync state: in sync,
syncing, offline, or sync error (with the error reason).
3.10. Backup and Restore
SC-FR-BAK-1
3.10.1. Whole-database backup export SC-FR-BAK-1 The app shall export, in one user action, a single file containing the
complete local application database needed to reconstruct app state on
another device: every ledger's full event log, its data key in join-code
form, key fingerprint, optional sync hint and recovery-acknowledged flag.
Export is whole-database, not per-ledger; it is distinct from the
per-ledger CSV export (SC-FR-EXR-*), which is a lossy accounting report.
A ledger whose data key is not present on this device cannot be restored
and shall be omitted from the file, with the user informed.
SC-FR-BAK-2
3.10.2. Restore with merge or replace SC-FR-BAK-2 The app shall restore from a backup file with the user choosing per
restore: (a) merge - union the selected ledgers into existing state, which
shall be idempotent (re-importing the same file changes nothing) and shall
never overwrite an existing ledger whose id matches but whose key differs
(key conflict is reported and skipped); or (b) replace - wipe the local
database, then restore the selected ledgers (clean device migration). The
user shall be able to select which ledgers in the file to restore.
SC-FR-BAK-3
3.10.3. Backup is unencrypted - user-owned risk SC-FR-BAK-3 The backup file is deliberately NOT encrypted and is human-readable
(JSON). It contains every ledger's join code (data key) in clear text;
anyone holding the file can decrypt every ledger in it. This is an
explicit, accepted exception to SC-ARC-ENC-*. Securing the exported file
is the user's responsibility, not the app's. The app shall state this
prominently at export time and require an explicit acknowledgement before
producing the file, and shall repeat the warning inside the file itself.
The app shall never transmit the backup to any network endpoint.
SC-FR-BAK-4
3.10.4. Backup version gating SC-FR-BAK-4 The backup file shall declare its own backup-format version and the
event-schema version its contents use. On restore the app shall refuse
loudly (no partial or best-effort import) if either exceeds the version
the app understands, telling the user to update the app; older versions
shall be accepted. Every event in the file shall be re-validated through
the canonical decoder before any data is written; any failure aborts the
whole restore. (Mirrors SC-ARC-FMT-2 for item (g).)
SC-FR-BAK-5
3.10.5. Restore preserves this device's identity SC-FR-BAK-5 A device restoring from a backup shall keep its own device identity and
shall never adopt the identity of the device that wrote the backup
(SC-ARC-IDN-1, SC-ARC-LOG-1 sole-writer). Restored events retain their
original authorship in history; new events append under the restoring
device. After a restore the user re-claims their participant per ledger
via the normal multi-device claim flow (SC-FR-PRT-2 (c)).
4. Storage and Sync Architecture
SC-ARC-PRV-1
4.1. Shared folder provider abstraction SC-ARC-PRV-1 All access to the shared storage shall go through an internal
"SharedFolderProvider" interface. The interface shall be defined narrowly
enough that multiple OneDrive back-ends (Microsoft Graph API; iOS Files /
Document Picker) and future non-OneDrive back-ends (iCloud Drive, Dropbox,
etc.) can coexist as independent implementations selectable at runtime,
with no changes required in the rest of the app when a new back-end is
added.
  • SC-ARC-PRV-3
    4.2. Microsoft Graph OneDrive provider (MVP) SC-ARC-PRV-3 The MVP shall ship exactly one SharedFolderProvider implementation: a
    Microsoft Graph API provider that authenticates the user via OAuth 2.0
    (Authorization Code with PKCE) and accesses the ledger folder directly
    through the Graph REST API. ETags returned by Graph shall be used to
    implement the conditional-write precondition in SC-ARC-PRV-2.

    The iOS Files / Document Picker provider that was considered alongside
    Graph API in earlier drafts is not buildable in a web app and is
    explicitly removed from MVP scope.
  • SC-ARC-PRV-1
    4.1. Shared folder provider abstraction SC-ARC-PRV-1 All access to the shared storage shall go through an internal
    "SharedFolderProvider" interface. The interface shall be defined narrowly
    enough that multiple OneDrive back-ends (Microsoft Graph API; iOS Files /
    Document Picker) and future non-OneDrive back-ends (iCloud Drive, Dropbox,
    etc.) can coexist as independent implementations selectable at runtime,
    with no changes required in the rest of the app when a new back-end is
    added.
SC-ARC-PRV-3
4.2. Microsoft Graph OneDrive provider (MVP) SC-ARC-PRV-3 The MVP shall ship exactly one SharedFolderProvider implementation: a
Microsoft Graph API provider that authenticates the user via OAuth 2.0
(Authorization Code with PKCE) and accesses the ledger folder directly
through the Graph REST API. ETags returned by Graph shall be used to
implement the conditional-write precondition in SC-ARC-PRV-2.

The iOS Files / Document Picker provider that was considered alongside
Graph API in earlier drafts is not buildable in a web app and is
explicitly removed from MVP scope.
SC-ARC-PRV-2
4.3. Required provider operations SC-ARC-PRV-2 The SharedFolderProvider interface shall offer at minimum:
- List files in the ledger folder (with last-modified timestamps and ETags or equivalent)
- Read a file by name
- Create or replace a file by name, with an optional precondition (ETag / If-Match)
- Delete a file by name
All operations shall be asynchronous and shall report transport errors
distinctly from semantic errors.
SC-ARC-LOG-1
4.4. Append-only per-device event logs SC-ARC-LOG-1 The authoritative state of a ledger shall be expressed as the union of
per-device, append-only event logs stored in the ledger folder. Each device
shall write only to log files that it owns (named with that device's UUID,
see SC-ARC-LOG-4). No device shall ever write to another device's log
files.
SC-ARC-LOG-2
4.5. Event types SC-ARC-LOG-2 The event log shall support at minimum the following event types:
- LedgerRenamed (changes the ledger's human-readable name; sensitive content lives here, not in the plaintext metadata file - see SC-FR-LED-3)
- ParticipantAdded
- ParticipantRenamed
- ParticipantClaimed (binds a device UUID to a participant UUID)
- LabelCreated
- LabelRenamed
- LabelDeleted (tombstone)
- ExpenseCreated (payload includes execution date, entry timestamp, and label UUID list)
- ExpenseUpdated (carries a full new version of the expense, including its label UUID list)
- ExpenseDeleted (tombstone)
- SettlementRecorded
- SettlementUpdated (carries a full new version of the settlement)
- SettlementDeleted (tombstone)
Each event shall carry: event UUID, event type, author device UUID, author
participant UUID, wall-clock timestamp (which is the entry timestamp for
expense events), schema version, and a type-specific payload.
SC-ARC-LOG-3
4.6. Event log file format SC-ARC-LOG-3 Event log segment files shall be UTF-8 encoded JSON Lines (one JSON object
per line, no trailing comma, newline-terminated). Files shall be
append-only: existing lines shall never be modified or removed, only new
lines appended. No compression shall be applied; segments are stored as
plain UTF-8 text.
SC-ARC-LOG-4
4.7. Segmented per-device logs SC-ARC-LOG-4 Each device's event log shall be split across one or more segment files in
the ledger folder. Segment files shall be named
"events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
is the UTC instant the segment was opened, formatted as
"YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
ordering matches chronological order.

A device shall have at most one "open" segment per ledger at any time; all
other segments belonging to that device are "closed" and immutable. New
events are appended to the open segment. When the open segment's size on
disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
the next event, the device shall:
1. Close the current segment (no further writes to it, ever).
2. Open a new segment with the current UTC timestamp as its filename.
3. Append the new event to the new segment.

A device's full event log is reconstructed by reading every segment file
under that device's folder, sorted by filename. The concatenation is the
device's complete, append-only history.
  • SC-ARC-LOG-5
    4.8. Segment size threshold SC-ARC-LOG-5 The segment size threshold shall be 1 MiB (1,048,576 bytes) for the MVP.

    This value is a configurable constant in the implementation, not a wire
    format invariant: a device that uses a different threshold remains fully
    interoperable with devices using the MVP value, because every device folds
    all segments regardless of how their boundaries were drawn.
  • SC-ARC-ENC-2
    4.18. Authenticated encryption of event-log segments SC-ARC-ENC-2 Every event-log segment file (SC-ARC-LOG-4) shall be encrypted with
    AES-256-GCM using the ledger's data key. The on-disk file format for an
    encrypted segment shall be:
    - 12 bytes: a freshly-generated random IV (nonce)
    - N bytes: the GCM ciphertext, where N equals the plaintext JSONL length
    - 16 bytes: the GCM authentication tag

    A fresh IV shall be generated for every encryption operation, including
    every re-upload of the same logical segment after an append. Random 96-bit
    IVs are acceptable because the collision probability over the lifetime of
    any realistic ledger is negligible.

    Decryption shall fail loudly on auth-tag mismatch. A segment that fails
    to decrypt shall be reported as an error to the user, never silently
    skipped, because it indicates either a wrong key, a corrupted file, or
    tampering.

    No plaintext segment file shall ever leave the device.
  • SC-ARC-LOG-4
    4.7. Segmented per-device logs SC-ARC-LOG-4 Each device's event log shall be split across one or more segment files in
    the ledger folder. Segment files shall be named
    "events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
    is the UTC instant the segment was opened, formatted as
    "YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
    ordering matches chronological order.

    A device shall have at most one "open" segment per ledger at any time; all
    other segments belonging to that device are "closed" and immutable. New
    events are appended to the open segment. When the open segment's size on
    disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
    the next event, the device shall:
    1. Close the current segment (no further writes to it, ever).
    2. Open a new segment with the current UTC timestamp as its filename.
    3. Append the new event to the new segment.

    A device's full event log is reconstructed by reading every segment file
    under that device's folder, sorted by filename. The concatenation is the
    device's complete, append-only history.
SC-ARC-LOG-5
4.8. Segment size threshold SC-ARC-LOG-5 The segment size threshold shall be 1 MiB (1,048,576 bytes) for the MVP.

This value is a configurable constant in the implementation, not a wire
format invariant: a device that uses a different threshold remains fully
interoperable with devices using the MVP value, because every device folds
all segments regardless of how their boundaries were drawn.
SC-ARC-LOG-6
4.9. No compaction, no compression, no data loss SC-ARC-LOG-6 The MVP shall not implement any form of log compaction, snapshotting, or
compression that would reduce or transform historical event data:
- Closed segments shall be retained verbatim for the lifetime of the
ledger. They shall never be merged, rewritten, or deleted by the app.
- Tombstone events (ExpenseDeleted, SettlementDeleted, LabelDeleted)
shall remain in the log alongside the events they tombstone.
- No segment shall be compressed (gzip, zstd, etc.) at rest.
- The derived-state cache (SC-ARC-CCH-1) is a performance optimisation
only; it shall be reconstructible at any time by folding all segments
from scratch.

The full event history is the source of truth and shall remain forensically
reconstructible from the segment files alone.
SC-ARC-MRG-1
4.10. Deterministic merge SC-ARC-MRG-1 The derived ledger state shall be computed by folding all event log files in
a deterministic order (events sorted by wall-clock timestamp, breaking ties
by event UUID lexicographic order). Given the same set of event log files,
all devices shall compute the same derived state.
SC-ARC-MRG-2
4.11. Last-write-wins on conflicting edits SC-ARC-MRG-2 If two devices independently issue ExpenseUpdated events targeting the same
expense, the event with the later wall-clock timestamp shall win in the
derived state. Ties shall be broken by event UUID lexicographic order. The
loser's event shall remain in the log for auditability.
SC-ARC-CCH-1
4.12. Local derived-state cache SC-ARC-CCH-1 Each device shall maintain a local cache of the derived ledger state and
of the per-segment read offsets (or hashes) used during the last fold.
Re-syncing shall only re-read segments whose remote state has changed, and
within a changed segment shall only fold the new tail. Closed segments
(SC-ARC-LOG-4) are immutable, so once their hash has been recorded they
shall not be re-fetched again.
SC-ARC-IDN-1
4.13. Device identity SC-ARC-IDN-1 On first launch the app shall generate a random version-4 UUID and persist
it in browser IndexedDB under the app's origin. This UUID shall be the
device's identity across all ledgers and shall not leave the device
except as the directory name of that device's log-segment folder inside
each shared OneDrive folder (SC-ARC-LOG-4).

If the IndexedDB record is lost (browser data cleared, private-browsing
session, iOS Safari long-disuse eviction, fresh browser install), a new
device UUID shall be generated on the next launch. The user shall be
prompted to claim a participant identity again as if it were a new device
(SC-FR-PRT-2). The previous device's log segments in the shared folder
remain valid history and shall continue to contribute to the derived
state; they simply receive no further appends.

A new device id does not have to mean a new participant: the same person
re-running the app (after eviction, or simply on an additional device)
re-claims their existing participant via SC-FR-PRT-2 (c). The participant
then has both device ids bound to it; balances and "you" attribution stay
on the one identity. Authorship is still per device id (SC-ARC-IDN-2);
only the participant↔device binding is many-to-one.
SC-ARC-IDN-2
4.14. No reliance on provider identity for authorship SC-ARC-IDN-2 The system shall not rely on the shared folder provider's identity model
(e.g. OneDrive user account) to identify the author of an event. Authorship
shall be established entirely by the device UUID and the claimed participant
UUID inside the event payload.
SC-ARC-HST-1
4.15. Static-asset hosting and zero backend SC-ARC-HST-1 The app code (HTML, CSS, JavaScript, manifest, icons, Service Worker)
shall be distributed as immutable static assets from a third-party static
hosting provider. The current target is GitHub Pages; equivalent
alternatives such as Cloudflare Pages, Netlify, or any S3-equivalent
object store are acceptable substitutes. The choice of host shall not
require code changes; only the deployment URL changes.

The project shall not operate any application server, database server,
job queue, or other persistent backend infrastructure. All ledger state
lives exclusively in (a) per-user OneDrive folders, accessed via Graph
(SC-ARC-PRV-3), and (b) per-device browser storage (SC-ARC-IDN-1,
SC-NFR-SEC-2).
SC-ARC-HST-2
4.16. Verifiable build SC-ARC-HST-2 The static bundle deployed to the hosting provider shall be reproducible
from a public source repository. The repository shall include the build
configuration, dependency lockfiles, and a release procedure that pins
the exact commit deployed for each released version. Subresource Integrity
(SRI) hashes shall be used on all script and style tags whose contents
are loaded from the static host, so a tampered asset is detected and
refused by the browser.
SC-ARC-ENC-1
4.17. Per-ledger data key SC-ARC-ENC-1 Every ledger created by the MVP shall be encrypted. At ledger creation
the device shall generate a fresh random 256-bit AES key (the "data key")
using the browser's cryptographically secure random number generator
(crypto.getRandomValues). The data key shall never be transmitted to or
stored on the shared folder provider in any form. It shall exist only
in volatile memory on devices that have joined the ledger and in
IndexedDB on those same devices (see SC-ARC-ENC-5).

The MVP shall not offer an "unencrypted ledger" option. Encryption is
mandatory.
  • SC-ARC-LOG-4
    4.7. Segmented per-device logs SC-ARC-LOG-4 Each device's event log shall be split across one or more segment files in
    the ledger folder. Segment files shall be named
    "events/<device-uuid>/<segment-open-timestamp>.jsonl", where the timestamp
    is the UTC instant the segment was opened, formatted as
    "YYYYMMDDTHHMMSSsss" (millisecond precision) so that lexicographic file
    ordering matches chronological order.

    A device shall have at most one "open" segment per ledger at any time; all
    other segments belonging to that device are "closed" and immutable. New
    events are appended to the open segment. When the open segment's size on
    disk would exceed the segment size threshold (SC-ARC-LOG-5) after writing
    the next event, the device shall:
    1. Close the current segment (no further writes to it, ever).
    2. Open a new segment with the current UTC timestamp as its filename.
    3. Append the new event to the new segment.

    A device's full event log is reconstructed by reading every segment file
    under that device's folder, sorted by filename. The concatenation is the
    device's complete, append-only history.
SC-ARC-ENC-2
4.18. Authenticated encryption of event-log segments SC-ARC-ENC-2 Every event-log segment file (SC-ARC-LOG-4) shall be encrypted with
AES-256-GCM using the ledger's data key. The on-disk file format for an
encrypted segment shall be:
- 12 bytes: a freshly-generated random IV (nonce)
- N bytes: the GCM ciphertext, where N equals the plaintext JSONL length
- 16 bytes: the GCM authentication tag

A fresh IV shall be generated for every encryption operation, including
every re-upload of the same logical segment after an append. Random 96-bit
IVs are acceptable because the collision probability over the lifetime of
any realistic ledger is negligible.

Decryption shall fail loudly on auth-tag mismatch. A segment that fails
to decrypt shall be reported as an error to the user, never silently
skipped, because it indicates either a wrong key, a corrupted file, or
tampering.

No plaintext segment file shall ever leave the device.
SC-ARC-ENC-3
4.19. Plaintext metadata and key fingerprint SC-ARC-ENC-3 The ledger metadata file (SC-FR-LED-3) shall remain plaintext so that a
joining device can identify the folder as a SplitClone ledger before
holding the key. The metadata file shall include a "key fingerprint":
the lowercase hexadecimal representation of the first 16 bytes of
SHA-256(data_key).

When a user enters a join code, the app shall compute the fingerprint of
the imported key and compare it to the stored fingerprint. A mismatch
indicates a wrong join code and shall be reported as such; the key shall
not be persisted in IndexedDB until the fingerprint matches.

The truncated SHA-256 does not reveal the key (256-bit input space, only
128 bits of output displayed) and is short enough to log or display in
diagnostics if needed.
SC-ARC-ENC-4
4.20. Join codes for key distribution SC-ARC-ENC-4 The app shall allow any device that already holds the data key to display
the key as a join code in two interchangeable forms:
(a) A QR code suitable for camera scanning device-to-device.
(b) A base64url-encoded string with a 4-character truncated-SHA-256
checksum appended (so typos are detected before the fingerprint
check in SC-ARC-ENC-3).

A device that has not yet joined a ledger shall accept the join code via
either form (scan-to-camera or paste). On successful import, the
fingerprint check (SC-ARC-ENC-3) shall pass before the key is persisted.

The join code shall never be transmitted by the app to any network
endpoint. It shall be communicated between users entirely out-of-band
(messaging app, in-person QR scan, etc.). The app shall include a clear
in-UI warning that the join code grants full access to the ledger's
contents and shall be shared only over a channel the user trusts.
SC-ARC-ENC-5
4.21. Local key storage SC-ARC-ENC-5 Once a device has imported a data key, the key shall be persisted in
IndexedDB under the app's origin, scoped to the ledger UUID. Where the
browser supports it, the key shall be imported as a non-extractable
CryptoKey via crypto.subtle.importKey so that crypto.subtle.exportKey on
the resulting object fails. The decryption and encryption operations
continue to work with non-extractable keys; only direct read-out of the
raw key material is prevented.

If the IndexedDB record holding the key is lost (browser data cleared,
private-browsing session, iOS Safari long-disuse eviction, fresh browser
install), the device shall be unable to read the ledger and the user
shall be required to re-enter the join code.
SC-ARC-ENC-6
4.22. Recovery-code backup prompt SC-ARC-ENC-6 Immediately after creating a ledger, and on every subsequent session
until acknowledged, the app shall prominently prompt the user to save
the join code as a recovery code in a safe place (their password
manager, a downloaded file, a printed copy). The prompt shall remain
visible until the user explicitly confirms they have saved it.

The app shall make it easy to:
- Reveal the current data key as a join code on demand from a joined
device.
- Download the join code as a small text file.
- Copy the join code to the clipboard.
The user shall be able to re-display the prompt at any later time from
the ledger settings.
SC-ARC-ENC-7
4.23. No key rotation or member revocation in MVP SC-ARC-ENC-7 The MVP shall not support data-key rotation, re-encrypting existing
segments under a new key, or revoking a member's access to an existing
ledger. A user who needs to revoke a former member shall be advised, via
in-UI guidance, to create a new ledger and re-enter the relevant expense
history manually (or by exporting and re-importing, when the relevant
import feature exists - see Roadmap).
SC-ARC-FMT-1
4.24. Scope of "file format" SC-ARC-FMT-1 The "file format" of a SplitClone ledger comprises every layout that the
app reads from or writes to non-volatile storage shared with users or
external tools. Specifically:
(a) The plaintext metadata file structure (SC-FR-LED-3).
(b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
(c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
(d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
(e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
(f) The CSV export schema (SC-FR-EXR-4).
(g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).

A single monotonically increasing integer schema version (declared in the
metadata file, SC-FR-LED-3) shall identify the current snapshot of the
format as a whole. A change to any of (a) - (f) requires incrementing the
schema version.

Item (g), the backup file, carries its OWN independent monotonically
increasing version integer ("splitcloneBackup"), declared inside the
backup file itself, plus an embedded copy of the (a)-(f) event-schema
version its contents are written in. It is versioned separately because a
backup is a self-describing one-shot artefact, not part of the
continuously-synced shared folder; a backup reader gates on those two
integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
file loudly; accept older). All other governance (SC-ARC-FMT-3,
changelog) applies to (g) unchanged.

The local browser-side caches (IndexedDB layout, derived-state cache
structure) are NOT part of the file format. They are private to a single
device and may evolve freely as long as the user-visible behaviour is
preserved.
  • SC-ARC-FMT-2
    4.25. Schema version handling SC-ARC-FMT-2 On reading a ledger, a device shall compare the schema version declared
    in the metadata file (the "ledger version") against the highest schema
    version it has been built to understand (the "device version"):

    - If ledger version > device version: the device shall refuse to fold,
    read, or write to the ledger. It shall display a clear error
    explaining that the ledger was written by a newer version of the app
    and shall suggest the user update the app. It shall not attempt
    partial reads or "best effort" parsing.

    - If ledger version == device version: normal operation.

    - If ledger version < device version: the device shall read all events
    and segments and shall perform any necessary upgrades to the older
    schemas in memory only. New events shall continue to be written at
    the ledger's current schema version (i.e. the older version), so
    that other devices still on the older app continue to participate.

    The ledger version shall never be incremented implicitly. Promoting a
    ledger to a newer schema version shall be an explicit user action
    ("Upgrade ledger format") that re-confirms compatibility implications
    and warns that older devices will stop being able to read the ledger
    until they update.

    Closed segments (SC-ARC-LOG-6) shall never be rewritten to convert their
    schema version. Schema upgrades apply to new events going forward, not
    to history.
  • SC-ARC-FMT-3
    4.26. Stability commitment post-v1.0 SC-ARC-FMT-3 Once SplitClone reaches its v1.0 release, the file format defined in
    SC-ARC-FMT-1 shall be treated as stable. After v1.0:

    - Any proposed change to the file format requires explicit, recorded
    approval from the project owner before implementation begins. Format
    changes shall not be introduced as part of unrelated work or as a
    side effect of refactoring.
    - The project shall maintain a written, public changelog that
    enumerates every defined schema version, the changes between
    consecutive versions, and the in-memory migration logic that an
    upgraded device applies when reading the older version.
    - Schema-version increments shall be infrequent and shall batch
    related changes together where possible, rather than incrementing
    repeatedly for small additions.

    Before v1.0, the format may evolve more freely, but every change shall
    still be a deliberate decision and shall update the changelog.
  • SC-ARC-FMT-1
    4.24. Scope of "file format" SC-ARC-FMT-1 The "file format" of a SplitClone ledger comprises every layout that the
    app reads from or writes to non-volatile storage shared with users or
    external tools. Specifically:
    (a) The plaintext metadata file structure (SC-FR-LED-3).
    (b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
    (c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
    (d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
    (e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
    (f) The CSV export schema (SC-FR-EXR-4).
    (g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).

    A single monotonically increasing integer schema version (declared in the
    metadata file, SC-FR-LED-3) shall identify the current snapshot of the
    format as a whole. A change to any of (a) - (f) requires incrementing the
    schema version.

    Item (g), the backup file, carries its OWN independent monotonically
    increasing version integer ("splitcloneBackup"), declared inside the
    backup file itself, plus an embedded copy of the (a)-(f) event-schema
    version its contents are written in. It is versioned separately because a
    backup is a self-describing one-shot artefact, not part of the
    continuously-synced shared folder; a backup reader gates on those two
    integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
    file loudly; accept older). All other governance (SC-ARC-FMT-3,
    changelog) applies to (g) unchanged.

    The local browser-side caches (IndexedDB layout, derived-state cache
    structure) are NOT part of the file format. They are private to a single
    device and may evolve freely as long as the user-visible behaviour is
    preserved.
SC-ARC-FMT-2
4.25. Schema version handling SC-ARC-FMT-2 On reading a ledger, a device shall compare the schema version declared
in the metadata file (the "ledger version") against the highest schema
version it has been built to understand (the "device version"):

- If ledger version > device version: the device shall refuse to fold,
read, or write to the ledger. It shall display a clear error
explaining that the ledger was written by a newer version of the app
and shall suggest the user update the app. It shall not attempt
partial reads or "best effort" parsing.

- If ledger version == device version: normal operation.

- If ledger version < device version: the device shall read all events
and segments and shall perform any necessary upgrades to the older
schemas in memory only. New events shall continue to be written at
the ledger's current schema version (i.e. the older version), so
that other devices still on the older app continue to participate.

The ledger version shall never be incremented implicitly. Promoting a
ledger to a newer schema version shall be an explicit user action
("Upgrade ledger format") that re-confirms compatibility implications
and warns that older devices will stop being able to read the ledger
until they update.

Closed segments (SC-ARC-LOG-6) shall never be rewritten to convert their
schema version. Schema upgrades apply to new events going forward, not
to history.
  • SC-ARC-FMT-1
    4.24. Scope of "file format" SC-ARC-FMT-1 The "file format" of a SplitClone ledger comprises every layout that the
    app reads from or writes to non-volatile storage shared with users or
    external tools. Specifically:
    (a) The plaintext metadata file structure (SC-FR-LED-3).
    (b) The event-log segment directory layout and filename pattern (SC-ARC-LOG-4).
    (c) The encrypted segment envelope (IV / ciphertext / GCM tag, SC-ARC-ENC-2).
    (d) The plaintext JSONL event schema inside a segment, including the payload schema of every event type (SC-ARC-LOG-2).
    (e) The join-code encoding, in both QR and base64url-with-checksum forms (SC-ARC-ENC-4).
    (f) The CSV export schema (SC-FR-EXR-4).
    (g) The local backup file schema (SC-FR-BAK-1; docs/backup-format.md).

    A single monotonically increasing integer schema version (declared in the
    metadata file, SC-FR-LED-3) shall identify the current snapshot of the
    format as a whole. A change to any of (a) - (f) requires incrementing the
    schema version.

    Item (g), the backup file, carries its OWN independent monotonically
    increasing version integer ("splitcloneBackup"), declared inside the
    backup file itself, plus an embedded copy of the (a)-(f) event-schema
    version its contents are written in. It is versioned separately because a
    backup is a self-describing one-shot artefact, not part of the
    continuously-synced shared folder; a backup reader gates on those two
    integers exactly as SC-ARC-FMT-2 gates the shared format (refuse a newer
    file loudly; accept older). All other governance (SC-ARC-FMT-3,
    changelog) applies to (g) unchanged.

    The local browser-side caches (IndexedDB layout, derived-state cache
    structure) are NOT part of the file format. They are private to a single
    device and may evolve freely as long as the user-visible behaviour is
    preserved.
SC-ARC-FMT-3
4.26. Stability commitment post-v1.0 SC-ARC-FMT-3 Once SplitClone reaches its v1.0 release, the file format defined in
SC-ARC-FMT-1 shall be treated as stable. After v1.0:

- Any proposed change to the file format requires explicit, recorded
approval from the project owner before implementation begins. Format
changes shall not be introduced as part of unrelated work or as a
side effect of refactoring.
- The project shall maintain a written, public changelog that
enumerates every defined schema version, the changes between
consecutive versions, and the in-memory migration logic that an
upgraded device applies when reading the older version.
- Schema-version increments shall be infrequent and shall batch
related changes together where possible, rather than incrementing
repeatedly for small additions.

Before v1.0, the format may evolve more freely, but every change shall
still be a deliberate decision and shall update the changelog.
5. Non-Functional Requirements
SC-NFR-PLT-1
5.1. Browser platform (MVP) SC-NFR-PLT-1 The MVP shall be a Progressive Web App running in modern evergreen
browsers, with no native iOS, Android, or desktop binary. Minimum
supported targets are:
- Mobile: iOS Safari 17+ (covers iPhone and iPad), Chrome on Android 14+
- Desktop: current and previous major versions of Chrome, Edge, Firefox, Safari

The app shall be installable to the home screen via Safari's "Add to Home
Screen" on iOS and via the standard PWA install prompt on Chromium
browsers, including a Web App Manifest declaring icon, name, theme
colour, and standalone display mode.
  • SC-NFR-PLT-2
    5.2. Responsive layout SC-NFR-PLT-2 The UI shall use a responsive layout that adapts to viewport widths from
    ~320 px (smallest iPhone) up to typical desktop browser widths. iPad and
    desktop are not separate code paths; they are simply wider breakpoints of
    the same layout. The phone-portrait viewport is the design baseline; other
    breakpoints are progressive enhancements.
  • SC-NFR-PLT-1
    5.1. Browser platform (MVP) SC-NFR-PLT-1 The MVP shall be a Progressive Web App running in modern evergreen
    browsers, with no native iOS, Android, or desktop binary. Minimum
    supported targets are:
    - Mobile: iOS Safari 17+ (covers iPhone and iPad), Chrome on Android 14+
    - Desktop: current and previous major versions of Chrome, Edge, Firefox, Safari

    The app shall be installable to the home screen via Safari's "Add to Home
    Screen" on iOS and via the standard PWA install prompt on Chromium
    browsers, including a Web App Manifest declaring icon, name, theme
    colour, and standalone display mode.
SC-NFR-PLT-2
5.2. Responsive layout SC-NFR-PLT-2 The UI shall use a responsive layout that adapts to viewport widths from
~320 px (smallest iPhone) up to typical desktop browser widths. iPad and
desktop are not separate code paths; they are simply wider breakpoints of
the same layout. The phone-portrait viewport is the design baseline; other
breakpoints are progressive enhancements.
SC-NFR-OFF-1
5.3. Offline-first SC-NFR-OFF-1 All read and write actions on the ledger shall be possible without an
internet connection. Local changes shall be queued in IndexedDB and pushed
to OneDrive automatically once connectivity is restored. A registered
Service Worker shall cache the static app shell so that the app launches
when offline.

Known limitation: iOS Safari evicts PWA storage (IndexedDB and Service
Worker caches) after extended periods of non-use (currently around 7
weeks). Users who open the app at least every few weeks will not be
affected; users returning after a long absence may need to re-download
the app shell and re-authenticate. The app shall detect lost local state
gracefully (re-fetch from OneDrive, re-prompt for OAuth) rather than
data-loss.
SC-NFR-PRF-1
5.4. Cold-start latency SC-NFR-PRF-1 After cold launch with an existing local cache, the expense list shall
render within 1 second on supported iPhone hardware. Network sync may
complete asynchronously after the UI is interactive.
SC-NFR-SEC-1
5.5. At-rest data SC-NFR-SEC-1 All ledger contents inside the shared folder shall be encrypted at rest
under a per-ledger 256-bit AES-GCM data key (see SC-ARC-ENC-1 through
SC-ARC-ENC-7). The only plaintext file in a ledger folder shall be the
minimal metadata file specified in SC-FR-LED-3.

The shared folder provider's own access controls (folder sharing,
permissions) remain in force and are the first line of defence; the
at-rest encryption layer is additional protection against accidental
over-sharing of the folder and against compromise or insider access at
the provider level. The two layers protect against different threats and
neither replaces the other.

Members who legitimately hold the data key (every joined device) can
read the full ledger. Encryption is not intended to compartmentalise
access between members of the same ledger.
SC-NFR-SEC-2
5.6. Local secrets SC-NFR-SEC-2 Provider credentials (Microsoft Graph OAuth refresh and access tokens) and
the device UUID shall be stored in browser-local storage scoped to the
app's origin: IndexedDB for refresh tokens and the device UUID, in-memory
or sessionStorage for short-lived access tokens. None of these values
shall ever be written to the shared OneDrive folder.

The MVP shall use the OAuth 2.0 Authorization Code flow with PKCE (no
client secret), as appropriate for a public single-page application.
Refresh tokens shall be requested with the smallest scope set that
satisfies the SharedFolderProvider operations (SC-ARC-PRV-2).

The MVP acknowledges that browser-local storage is materially weaker than
a hardware-backed secure enclave (e.g. iOS keychain):
- IndexedDB contents are readable by any JavaScript executing on the same origin (XSS risk).
- No biometric gating is available to the browser.
- Browser extensions with sufficient permissions may inspect storage.
Mitigations: a strict Content Security Policy restricting which sources
can execute scripts; no third-party analytics or trackers (SC-NFR-PRV-1);
keeping refresh-token lifetime as short as the OAuth provider permits;
clearing tokens on sign-out.
SC-NFR-PRV-1
5.7. No third-party telemetry SC-NFR-PRV-1 The app shall not send analytics, crash reports, or usage data to any third
party. Diagnostic information shall remain on-device unless the user
explicitly exports it.
SC-NFR-LOC-1
5.8. Language SC-NFR-LOC-1 The MVP shall ship with English UI text only. The codebase shall route all
user-facing strings through an iOS localisation mechanism so that other
languages can be added later without code changes.
6. Roadmap Beyond MVP
7. Open Questions