Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,27 @@ flowchart TD
HeadFresh -- No --> Revalidate["Authenticated HEAD revalidation<br/>with If-None-Match"]
Revalidate --> HeadResult{"HEAD result"}
HeadResult -- "304" --> Resolve
HeadResult -- "Changed" --> Objects
HeadResult -- "Changed" --> DecodeHead
HeadResult -- "Network unavailable<br/>and cached HEAD usable" --> Resolve
HeadResult -- "Network unavailable<br/>and no usable cache" --> ReadError["Return explicit read error"]
Resolve --> ValuesCached{"Required immutable values cached?"}
ValuesCached -- Yes --> Plan["Validate document or query plan,<br/>predicate, ordering, projection, and limit"]
ValuesCached -- No --> Objects
ValuesCached -- No --> Objects["Revalidate read grant and return<br/>missing immutable TDB1 objects"]

BundleCheck -- Yes --> Bundle["Authority revalidates read grant,<br/>reads encrypted HEAD and required objects"]
Bundle --> BundleLimit{"At most 4 objects and 4 MiB<br/>with authenticated size metadata?"}
BundleLimit -- Yes --> Decoded["Return decoded cache values<br/>over HTTPS with no-store"]
BundleLimit -- No --> Objects
BundleCheck -- No --> Objects["Revalidate read grant and return<br/>authenticated TDB1 objects"]
Objects --> BrowserDecrypt["Browser decrypts and validates<br/>HEAD, index, snapshot, or trie objects"]
BundleLimit -- No --> FetchHead
BundleCheck -- No --> FetchHead["Revalidate read grant and return<br/>authenticated TDB1 HEAD"]
FetchHead --> DecodeHead["Browser decodes, decrypts when required,<br/>and validates HEAD within the safety limit"]
DecodeHead --> CacheHead["Encrypt decoded HEAD with device key<br/>and update the scoped cache"]
CacheHead --> Resolve
Objects --> BrowserDecrypt["Browser decodes, decrypts when required,<br/>and validates index, snapshot, or trie objects"]
BrowserDecrypt --> DecodedLimit{"Decoded object within<br/>configured safety limit?"}
DecodedLimit -- No --> LimitError["Return explicit decoded-size error"]
DecodedLimit -- Yes --> DeviceCache
Decoded --> DeviceCache["Encrypt decoded values with device key<br/>and update the scoped cache"]
BrowserDecrypt --> DeviceCache --> Plan
DeviceCache --> Plan
Plan --> ReadResult["Document, or bounded query result<br/>with point, index, or scan plan"]
```

Expand All @@ -96,29 +102,30 @@ flowchart TD
2. Cold point reads can use one
bounded decoded bundle when explicitly enabled; every ineligible or failed
bundle falls back to authenticated TDB1 object reads.
3. Queries remain bounded and report whether they used a point, declared
index, covering projection, or collection scan plan.
3. Queries remain bounded and report a point, index, or scan plan. Explicit
selected fields can be served from a covering index without full-document
reads.

### Mutation and cache-synchronisation flow

```mermaid
flowchart TD
Mutation["Create, replace, delete, restore, purge,<br/>scope erase, or index rebuild"]
Mutation["Create, replace, delete,<br/>or restore one document"]
Mutation --> Request["Session + CSRF + exact Origin<br/>scope + layout generation"]
Request --> Guards{"Operation allowed by maintenance state<br/>and generation current?"}
Guards -- No --> Reject["Return explicit maintenance<br/>or layout-changed error"]
Guards -- Yes --> Grant["Reload user and current write or admin grant"]
Grant --> Authorised{"Authorised?"}
Authorised -- No --> Deny["Return explicit forbidden response"]
Authorised -- Yes --> Load["Read current HEAD and affected immutable objects"]
Load --> Validate["Validate route, ID, document, limits,<br/>layout, and complete index configuration"]
Validate --> Immutable["Create immutable document, root,<br/>and index objects"]
Load --> Validate["Validate route, ID, document, decoded-object limit,<br/>layout, and complete index configuration"]
Validate --> Immutable["Create immutable layout<br/>and index objects"]
Immutable --> Publish["Publish one HEAD with If-Match"]
Publish --> Conflict{"ETag conflict?"}
Conflict -- Yes --> Retry{"Bounded retry remains?"}
Retry -- Yes --> Load
Retry -- No --> ConflictError["Return explicit conflict"]
Conflict -- No --> Commit["Return committed values and new ETag"]
Conflict -- No --> Commit["Return committed cache-value bundle"]
Commit --> Cache["Update the current scoped cache"]
Cache --> Tabs["Notify other tabs through BroadcastChannel"]
Tabs --> Result["Committed mutation result"]
Expand All @@ -128,6 +135,9 @@ flowchart TD
index objects, and publishes their references through one conditional HEAD.
2. Successful writes update the current cache and notify other tabs. Logout
revokes the session and clears the affected browser cache namespace.
3. Administrative purge, scope erase, index rebuild, and layout migration use
separate guarded endpoints. They do not return the ordinary document
mutation bundle shown here.

### Deployment, scaling, and storage flow

Expand Down
25 changes: 14 additions & 11 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,20 @@ encryption. Private scopes use a versioned AES-256-GCM data key.
immutable-object bundle in one browser request.
3. Older authorities, legacy metadata, oversized bundles, and cache hits use
the individual encrypted-object path.
4. If a cached HEAD TTL expired, the browser revalidates it with
4. Each individual object is decoded with a 16 MiB default safety limit before
JSON parsing or cache insertion.
5. If a cached HEAD TTL expired, the browser revalidates it with
`If-None-Match`.
5. A 304 response keeps the current layout generation.
6. ID equality resolves directly to one document path.
7. A matching declared index resolves a bounded set of candidate IDs.
8. An explicit `.select(...)` can use declared covering fields without
6. A 304 response keeps the current layout generation.
7. ID equality resolves directly to one document path.
8. A matching declared index resolves a bounded set of candidate IDs.
9. An explicit `.select(...)` can use declared covering fields without
loading full documents.
9. Queries without a usable index use an explicitly bounded scan.
10. Trie HEAD points to an immutable root, branch, and leaf path.
11. Snapshot HEAD points to one immutable collection snapshot.
12. The browser coalesces concurrent reads of the same immutable object.
13. It re-evaluates the complete predicate, orders, limits, and returns the
10. Queries without a usable index use an explicitly bounded scan.
11. Trie HEAD points to an immutable root, branch, and leaf path.
12. Snapshot HEAD points to one immutable collection snapshot.
13. The browser coalesces concurrent reads of the same immutable object.
14. It re-evaluates the complete predicate, orders, limits, and returns the
query plan with the documents.

Immutable pages do not need revalidation. Their object key identifies their
Expand All @@ -101,7 +103,8 @@ private.

1. The browser sends a mutation to the authority.
2. The authority authenticates the session and resolves allowed scopes.
3. Application validation runs before storage work.
3. Application validation and the decoded-object limit run before storage
publication.
4. Changed trie pages or the next immutable snapshot are serialised,
gzip-compressed when useful, and encrypted.
5. Every configured secondary index and declared covering projection is
Expand Down
118 changes: 71 additions & 47 deletions docs/DIAGRAMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,60 @@ index, deletion, migration, and deployment paths.
## Trust boundaries

```mermaid
flowchart LR
flowchart TB
subgraph Browser["Browser trust boundary"]
direction LR
UI["Application UI"]
Client["ThimbleDB client"]
Memory["Scope-namespaced memory cache"]
IDB["Scope-namespaced, device-key-encrypted IndexedDB"]
Cache["Scope-namespaced memory<br/>and encrypted IndexedDB"]
ReadKeys["Non-extractable decrypt-only scope keys"]

UI --> Client
Client --> Memory
Client --> IDB
Client --> Cache
Client --> ReadKeys
end

subgraph Authority["Application authority boundary"]
Exchange["OIDC token exchange"]
Session["Opaque revocable session"]
Authorizer["Current scope-grant calculation"]
direction LR
Identity["OIDC exchange<br/>and opaque session"]
Access["Current scope grants<br/>and key derivation"]
Broker["Authenticated ciphertext-object and decoded-bundle broker"]
Mutation["Mutation validation and execution"]

Exchange --> Session
Session --> Authorizer
Authorizer --> Broker
Authorizer --> Mutation
Identity --> Access
Access --> Broker
Access --> Mutation
end

subgraph DataStore["Private data object storage"]
direction LR
Heads["Mutable collection HEAD records"]
Pages["Immutable encrypted document and index pages"]
end

subgraph AuthStore["Separate private authentication storage"]
Identities["Identity mappings and users"]
Sessions["Session digests and CSRF state"]
Limits["Authentication rate-limit state"]
direction LR
AuthRecords["Identity mappings, session digests,<br/>CSRF, and rate-limit state"]
end

subgraph Platform["Platform secret boundary"]
direction LR
Master["Deployment master key"]
Provider["Storage credential or platform binding"]
end

UI -- "Session cookie + CSRF + requested scope" --> Authority
Client -- "Ciphertext objects or opt-in decoded bundles" --> Broker
UI -- "OIDC token" --> Identity
Client -- "Mutation + CSRF" --> Mutation
Client -- "Read + scope" --> Broker
Broker -- "Ciphertext or bundle" --> Client
Access -- "Scope keys" --> ReadKeys
Broker --> Heads
Broker --> Pages
Mutation -- "Create immutable objects" --> Pages
Mutation -- "Conditional HEAD publication" --> Heads
Exchange --> Identities
Session --> Sessions
Exchange --> Limits
Master --> Mutation
Master --> ReadKeys
Master --> AuthStore
Mutation -- "Immutable writes" --> Pages
Mutation -- "CAS HEAD" --> Heads
Identity --> AuthRecords
Master -- "Derived keys" --> Access
Access --> AuthRecords
Provider --> Broker
Provider --> Mutation
```
Expand Down Expand Up @@ -175,6 +174,7 @@ flowchart TD
Bundle{"Cold cache and bundle endpoint available?"}
BundleRead["One bounded authority read bundle"]
BundleValidate["Validate bundled HEAD and immutable values"]
BundleCache["Apply bundled values to the scoped cache"]
Index{"Matching declared index with indexable values?"}
PointHead["Read collection HEAD"]
IndexHead["Read collection HEAD"]
Expand All @@ -190,8 +190,11 @@ flowchart TD

Query --> Validate --> Point
Point -- Yes --> Bundle
Bundle -- Yes --> BundleRead --> BundleValidate --> Result
Bundle -- No or fallback --> PointHead --> ReadDocs
Bundle -- Yes --> BundleRead --> BundleAccepted{"Bundle accepted?"}
BundleAccepted -- Yes --> BundleValidate --> BundleCache --> Result
BundleAccepted -- "Unavailable, legacy,<br/>or over limit" --> PointHead
Bundle -- No --> PointHead
PointHead --> ReadDocs
Point -- No --> Index
Index -- Yes --> IndexHead --> IndexPage --> Candidates --> Covered
Covered -- Yes --> Projection --> Predicate
Expand All @@ -218,35 +221,51 @@ sequenceDiagram
Client->>Client: Validate query and choose point, index, or scan plan
Client->>Cache: Read collection HEAD

alt Cold point read and bundle endpoint advertised
alt Eligible cold point read and bundle endpoint advertised
Client->>Broker: GET bounded point-read bundle
Broker->>Broker: Require current read grant and enforce object/byte limits
Broker-->>Client: Decoded HEAD and immutable cache values over HTTPS
Client->>Cache: Store returned values with device-key encryption
alt Bundle accepted
Broker-->>Client: Decoded HEAD and immutable cache values over HTTPS
Client->>Cache: Store returned values with device-key encryption
else Bundle unavailable, legacy, or oversized
Broker-->>Client: Explicit fallback status
Client->>Broker: GET encrypted HEAD with session
Broker->>Store: Read object with ETag condition
Store-->>Broker: Encrypted HEAD
Broker-->>Client: Encrypted HEAD
Client->>Client: Decode TDB1 within the per-object limit
Client->>Cache: Store decoded value under device-key encryption
end
else HEAD missing or stale
Client->>Broker: GET encrypted HEAD with session
Broker->>Store: Read object with ETag condition
Store-->>Broker: Encrypted HEAD
Broker-->>Client: Encrypted HEAD
Client->>Cache: Store decrypted value under device-key encryption
Client->>Client: Decode TDB1 within the per-object limit
Client->>Cache: Store decoded value under device-key encryption
else Fresh cached HEAD
Client->>Cache: Use cached HEAD
end

alt Index plan
Client->>Cache: Read referenced immutable index page
Client->>Broker: Fetch index page on cache miss
Client->>Client: Resolve candidate IDs within maxScan
alt Explicit projection is covered
Client->>Client: Validate predicate and order from declared projections
Client->>Client: Build projected documents without full-document reads
else Full documents required
Client->>Cache: Resolve snapshot once or shared trie nodes
Client->>Broker: Fetch only missing immutable objects
opt Non-point query
alt Index plan
Client->>Cache: Read referenced immutable index page
Client->>Broker: Fetch index page on cache miss
Client->>Client: Resolve candidate IDs within maxScan
alt Explicit projection is covered
Client->>Client: Validate predicate and order from declared projections
Client->>Client: Build projected documents without full-document reads
else Full documents required
Client->>Cache: Resolve snapshot once or shared trie nodes
Client->>Broker: Fetch only missing immutable objects
end
else Scan plan
Client->>Client: Enforce bounded collection scan
end
else Scan plan
Client->>Client: Enforce bounded collection scan
end

Client->>Client: Decrypt when required, validate candidates, order, and limit
Client->>Client: Decode fetched TDB1 objects within the per-object limit
Client->>Client: Validate candidates, order, and limit
Client-->>App: Documents plus point/index/scan plan
```

Expand All @@ -265,14 +284,19 @@ sequenceDiagram
Authority->>Auth: Reload user and recalculate grants
Auth-->>Authority: Current write grant or denial
Authority->>Store: Read HEAD and affected immutable objects
Authority->>Authority: Validate document and update configured indexes
Authority->>Store: Create immutable document/root objects
Authority->>Authority: Validate document, decoded-object limit,<br/>and configured indexes
Authority->>Store: Create immutable layout objects
Authority->>Store: Create immutable index pages
Authority->>Store: Publish one HEAD with all document and index references using If-Match

alt ETag conflict
Store-->>Authority: Precondition failed
Authority->>Store: Reload current HEAD and retry
Authority->>Authority: Increment bounded retry count
alt Retry remains
Authority->>Store: Reload current HEAD and retry
else Retry exhausted
Authority-->>App: Explicit conflict
end
else Commit
Store-->>Authority: New HEAD ETag
Authority-->>App: Committed object bundle
Expand Down
1 change: 1 addition & 0 deletions site/public/diagrams/0c8435b9462cd3a63ff5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions site/public/diagrams/79ad5efd6e4ed3f2a894.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion site/public/diagrams/897ea0c26deb6b3cb03a.svg

This file was deleted.

1 change: 1 addition & 0 deletions site/public/diagrams/bdfadb8fe6895bdd60be.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion site/public/diagrams/c5bc1a796ce8e93f2840.svg

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion site/public/diagrams/f9b79a62f37a16efb8ec.svg

This file was deleted.

Loading