clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

docs: repositories share the SQLite allowance and portable backups preserve Git history

Dami committed 557b36f
commit
557b36f00744ef2351ff963090d5f7e5711df770
parent
c33701a0162394c53061d136fdf22ac2c39295eb
author
Dami <dami.etoile@gmail.com>
11 files changed, +145 -132
MREADME.md4+-
Mdocs/02-understanding-fuel.md26+++++----
Mdocs/04-data-and-names.md2+-
Mdocs/10-architecture.md30++++++----
Mdocs/12-develop-extend.md7++-
Mdocs/13-scripts-and-agents.md2+-
Mdocs/14-http-reference.md23++++----
Mdocs/15-costs-and-margins.md89++++++++++++++++--------------
Mdocs/16-hosting-without-cloudflare.md1+
Mdocs/22-grasp-01-git-hosting.md87+++++++++++++----------------
Msrc/landing.ts6+-
M README.md +2 -2
@@ -19,7 +19,7 @@ That answers with a relay at a memorable name, open to anyone for 14 days. Claim
1919 - **A community.** Members, invite links, an invite tree, moderators with a log of what they did, a signed roster and one NIP-29 group per relay, so group-aware clients show it as a place.2020 - **A site.** Notes and articles as pages with link previews, an Atom feed, and a card with a QR of the group address. NIP-5A also serves root, named and snapshot websites from signed manifests on isolated hostnames, with verified files, optional mirroring and sign-in that follows the relay's read rule.2121 - **Encrypted group transport.** Opt-in Marmot KeyPackages and opaque MLS group messages, with account admission and storage caps for ephemeral authors. Clients manage encryption and group membership.22-- **A Git host.** Opt-in GRASP-01 Smart HTTP, backed by [ntig](https://github.com/FelineStateMachine/ntig). Signed NIP-34 state authorizes branch and tag changes; events wait for their Git objects before publication. Hosting has [explicit storage and transaction limits](docs/22-grasp-01-git-hosting.md#git-http), and the owner can request a bounded read-only inventory without authorizing deletion.22+- **A Git host.** Opt-in GRASP-01 Smart HTTP, backed by [ntig](https://github.com/FelineStateMachine/ntig). Signed NIP-34 state authorizes branch and tag changes; events wait for their Git objects before publication. Hosting has [explicit storage limits](docs/22-grasp-01-git-hosting.md#git-http), and the owner can request a bounded read-only inventory without authorizing deletion.2323 - **A file host.** Blossom and NIP-96 on the same bucket, with mirroring, pre-flight checks and file reports.2424 - **A console.** Nine tabs on the relay's own page, signed by a browser extension or a remote signer app on a phone. Presets set up an outbox, an inbox, a private relay, a chat, a search replica, a media host or a quiet relay with every costly feature off, in one click. The template collection also includes static sites, public or member-published Marmot transport and Git repositories.2525 - **A file.** Everything the console sets, as one document with a schema: check it offline, see what applying would change, apply it, pull it back. Keep your relay in your repository. The presets are the same files, in `relay-templates/`.
@@ -31,7 +31,7 @@ That answers with a relay at a memorable name, open to anyone for 14 days. Claim
3131 3232 It runs on Cloudflare Workers with one Durable Object per name, SQLite inside it and R2 beside it. The same code runs without Cloudflare, on [celld](https://celld.dev) with an S3 bucket of your own; that path is checked on demand and [documented](docs/16-hosting-without-cloudflare.md).3333 34-Site hosting and mirroring are on by default; Marmot and GRASP are off until enabled. Their storage shares the existing fuel allowances: manifests and messages count as events, while mirrored files and retained Git objects count as files. The Health tab and public `/fuel` endpoint show aggregate usage, prices and balance. Traffic is visible but uncharged; provider operations are not itemized there. See [Understanding fuel](docs/02-understanding-fuel.md) and [Costs and margins](docs/15-costs-and-margins.md).34+Site hosting and mirroring are on by default; Marmot and GRASP are off until enabled. Their storage shares the existing fuel allowances: manifests, messages and SQLite-backed Git repositories count as events, while mirrored site files count as files. The Health tab and public `/fuel` endpoint show aggregate usage, prices and balance. Traffic is visible but uncharged; provider operations are not itemized there. See [Understanding fuel](docs/02-understanding-fuel.md) and [Costs and margins](docs/15-costs-and-margins.md).3535 3636 ## Docs3737
M docs/02-understanding-fuel.md +14 -12
@@ -36,28 +36,30 @@ Dumps, the JSONL files the Data tab can write on a schedule, live next to upload
3636 3737 **Rows written** is database work. A note costs a few rows. Reactions and other small events cost about the same.3838 39-The newer doors use these same meters. NIP-5A files mirrored into the relay and40-GRASP Git objects are part of **Files stored**; a site cache miss, site mirror,39+The newer doors use these same meters. NIP-5A files mirrored into the relay are40+part of **Files stored**. Git repositories store compressed chunks, refs and41+receipts in the relay's SQLite database, so they are part of **Events stored**.42+A site cache miss, site mirror,4143 Git clone or Git push also wakes the relay and can write events or accounting4244 rows. Marmot KeyPackages and encrypted group envelopes are ordinary stored4345 events, so they use **Events stored** and **Rows written**. HTTP request and4446 response bytes are shown as traffic for your information, but traffic has no4547 fuel price. There is no separate tenant charge for a site, Marmot or GRASP46-request. The relay's existing write, read, storage and rate rules still decide47-whether the request is admitted.48+request. There is no separate tenant charge for Git requests.4849 49-GRASP allows 4 MiB per pack, 16 MiB packed history and 128 transactions per50-repository, with at most 16 repositories and 320 MiB retained Git data per51-relay. These limits share the existing file allowance rather than adding a50+GRASP keeps bounded object, decoded-pack, retained-history, ref and repository51+limits. It has no transaction-count ceiling, but it still bounds52+its object, byte and receipt work. These limits share the existing storage allowances rather than adding a5253 new one. Signed events wait for their required Git objects before publication.5354 Deleting a ref or expiring metadata does not reclaim immutable Git history;54-those bytes remain in file storage until the relay is deleted. See55+those bytes remain in SQLite until the relay is deleted. See5556 [GRASP-01 Git hosting](22-grasp-01-git-hosting.md) for the full limits.5657 57-Git file usage includes transaction receipts and retained bookkeeping as58-well as packed files. It is not a fixed reservation per repository: the59-total grows with its history, including ref-only updates. The Health tab60-shows the combined file total, not a breakdown of these Git components.58+Git storage includes transaction receipts and retained bookkeeping as well as59+objects. It is not a fixed reservation per repository: the total grows with60+its history, including ref-only updates. The Health tab shows the combined61+storage total. The `gitstorage` management method gives owners a bounded62+backend-specific breakdown without authorizing deletion.6163 6264 ## Prices past the allowance6365
M docs/04-data-and-names.md +1 -1
@@ -47,7 +47,7 @@ Under **Dumps**, choose daily or weekly and how many to keep, seven by default.
4747 4848 ## Portable backups4949 50-On the Data tab, **Create and download** makes a private archive with configuration, signed events, saved list history, hidden and pending state, site/media blobs and Git objects. It also preserves optional GRASP PR expiry rows (`state.prRefs`), including deletion tombstones with `until: 0`, so deadlines survive restore. Existing archives can be downloaded or deleted. Each binary object and the archive have SHA-256 integrity checks; event signatures are checked on restore. Stored archives count toward fuel. Older archives without `state.prRefs` remain readable.50+On the Data tab, **Create and download** makes a private archive with configuration, signed events, saved list history, hidden and pending state, site/media blobs and Git objects. It also preserves optional GRASP PR expiry rows (`state.prRefs`), including refs with no pending expiry (`until: 0`), so deadlines survive restore. Existing archives can be downloaded or deleted. Restore verifies archive integrity, media hashes, Git object identities and event signatures. Git snapshots preserve compressed objects, refs and retry receipts; their repository identities and object graphs are checked before the database changes. Stored archives count toward fuel. Older archives without `state.prRefs` remain readable.5151 5252 Open a fresh, unclaimed relay and choose the archive in **Restore a backup**. Sign with the original owner's key, preview its source identity, counts and configuration, then restore. The target must be empty and unleased. Restore stages files before applying the database in one transaction. The source identity is recorded; the target gets a new relay key. Signed site and Git references still name their original URLs, so publish updated service references from your client after moving.5353
M docs/10-architecture.md +19 -11
@@ -160,7 +160,7 @@ reach relay doors.
160160 and state parsing, recursive maintainers, exact ref comparison and the161161 temporary `refs/nostr/<event-id>` namespace. The Git Smart HTTP door uses the162162 accepted state as its authorization boundary and passes Git objects through163-the object-store seam. It stays separate from the NIP-5A site door, so a site163+the repository contract. It stays separate from the NIP-5A site door, so a site164164 hostname cannot reach relay events or repository data.165165 166166 `repository-access.ts` gives one live Durable Object operation the repository
@@ -171,21 +171,29 @@ operation receives a retry response. Async context carries the token, so the
171171 fence replaces separate Git and control flags without becoming a durable lock.172172 Management reads and verifies the request before taking ownership, so an173173 incomplete unauthenticated body cannot hold the relay's admission token.174-It does not coordinate a crashed instance, another R2 client or another object175-using the prefix, and it never authorizes garbage collection.174+SQLite transactions provide the durable publication boundary. The admission175+token only coordinates in-flight work.176176 177177 `git-storage.ts` implements the owner-only `gitstorage` management method. It178-walks one accepted repository's physical R2 objects within explicit budgets,179-compares them with the live dependency inventory and reports physical, live,180-unreferenced and unknown objects by class alongside SQL reservations and their181-difference from listed bytes. An incomplete, over-budget or changed-root walk182-returns an error and no partial report. The manual scan has a 60-second183-per-instance cooldown and does not delete or reclaim storage.178+reads one accepted repository's SQLite metadata within explicit budgets and179+reports its object count, raw bytes, compressed bytes, metadata bytes, ref180+count, receipt count and the relay's complete physical database size. The181+physical database includes relay metadata and indexes, so the logical Git182+figures are diagnostics and are not billed again. The manual scan has a183+60-second per-instance cooldown and does not delete or reclaim storage.184+185+Git repositories use compressed chunk rows in the relay's SQLite database.186+The database size is charged once through `eventBytes`, which includes its187+indexes and relay metadata. The SQLite backend removes the former R2 per-push188+root and transaction ceiling, but it does not promise lower cost for every189+repository. SQLite storage, compression, rows, indexes, awake time and request190+work all affect the result. Portable backups export Git through the same191+bounded section because a Durable Object database is not a portable archive.184192 185193 [Tangled knots](https://docs.tangled.org/knot-self-hosting-guide) provide186194 useful prior art: AT Protocol identity sits above a filesystem Git host with187-repository maintenance. Bindws instead publishes immutable objects through188-an R2 root. The storage layout and recovery contract determine which old195+repository maintenance. Bindws publishes immutable objects through compressed196+SQLite chunks. The storage layout and recovery contract determine which old189197 objects can be reclaimed; decentralized identity does not require retaining190198 every superseded manifest. A collection protocol still needs to protect old191199 readers, unpublished writes and retained transaction receipts.
M docs/12-develop-extend.md +6 -1
M docs/13-scripts-and-agents.md +1 -1
M docs/14-http-reference.md +11 -12
@@ -102,24 +102,23 @@ Upload-pack advertises `allow-reachable-sha1-in-want`,
102102 `allow-tip-sha1-in-want` and `filter`, and accepts `blob:none` and `tree:0`.103103 An unknown `refs/nostr/<event-id>` has a 20-minute holding window. Expired104104 unmatched refs are hidden and scheduled for deletion, but immutable Git105-objects remain in retained storage. The 128-transaction ceiling can prevent106-physical cleanup; it does not make those bytes free.105+objects remain in retained storage. Expiring a ref does not make those bytes106+free.107107 108108 The owner can inspect one accepted repository through the NIP-86109109 `gitstorage` method at the relay root. It performs a bounded, read-only walk110-of the physical R2 prefix and compares it with the live Git dependency set.111-The result reports physical, live, unreferenced and unknown objects by class,112-SQL reservations and `reservationMinusListedBytes`, together with operation113-counts for diagnostics, limits and capture time. These counts are not tenant114-billing meters. Unreferenced does not authorize deletion:115-there is no collector or automatic storage reclamation. A complete report is116-required, so a changed root or any inventory budget exhaustion returns an117-error instead of partial data. The method has a 60-second per-instance118-cooldown; a Durable Object restart or eviction resets that cooldown.110+of SQLite metadata and reports the backend, object count, raw bytes, compressed111+bytes, metadata bytes, ref count, receipt count and the relay's complete112+`physicalDatabaseBytes`. The physical database total includes all relay data,113+indexes and Git data, so the diagnostic object bytes are not billed again.114+The method reports zero R2 gets and lists for this backend. Its SQL metadata115+reads contribute to the ordinary row meter. It changes no stored objects, refs116+or receipts and has a 60-second per-instance cooldown; a Durable Object117+restart or eviction resets that cooldown.119118 120119 | Method | Parameters | Auth | Answers | Status |121120 |---|---|---|---|---|122-| `gitstorage` | repository owner hex pubkey, identifier | NIP-98, owner with the storage action | bounded physical and live inventory, reservations and limits | 200; 400 invalid parameters; 403 inactive, fuel or role; 404 feature or repository unavailable; 413 inventory limit; 429 cooldown or another repository operation; 503 storage unavailable |121+| `gitstorage` | repository owner hex pubkey, identifier | NIP-98, owner with the storage action | bounded SQLite object, ref and receipt inventory, physical database size and limits | 200; 400 invalid parameters; 403 inactive, fuel or role; 404 feature or repository unavailable; 429 cooldown or another repository operation |123122 124123 The owner-only `storagestats` method also includes `result.graspSync` when125124 GRASP synchronization is configured. It reports `enabled`, `partial`, event
M docs/15-costs-and-margins.md +47 -42
@@ -20,10 +20,10 @@ prices, since another deployment can set different rates and allowances.
2020 2121 | Fuel line | Usage and allowance fields | What the meter includes |2222 |---|---|---|23-| Events stored | `eventBytes`, `freeEventBytes` | Allocated SQLite storage, including relay metadata as well as events |24-| Files stored | `mediaBytes`, `freeMediaBytes` | Uploaded files, dumps, imports, mirrored site files and reserved Git storage |23+| Events stored | `eventBytes`, `freeEventBytes` | Allocated SQLite storage, including relay metadata, events and SQLite-backed Git repositories |24+| Files stored | `mediaBytes`, `freeMediaBytes` | Uploaded files, dumps, imports and mirrored site files |2525 | Time awake | `activeMs`, `freeActiveMs` | An estimate from relay entrypoints, including client requests, jobs and alarms |26-| Rows written | `rowsWritten`, `freeRowsWritten` | Writes counted by the relay's event store |26+| Rows written | `rowsWritten`, `freeRowsWritten` | SQLite rows written by event storage and the Git object backend |2727 2828 `rates` gives storage prices in sats per GB-month, awake time in sats per2929 hour and writes in sats per million rows. Balance and credited or charged
@@ -34,13 +34,17 @@ use the usage accumulated in `month` against their monthly allowances.
3434 `bytesIn`, `bytesOut` and `rowsRead` are diagnostics with no fuel price.3535 A request can still increase awake time or stored data even when its traffic3636 is unpriced. Sites, Marmot and Git share the existing allowances; enabling a37-feature creates neither a separate allowance nor a request surcharge.37+feature creates neither a separate allowance nor a request surcharge. Git38+repositories use the relay's SQLite allowance. The configured39+public rates remain separate: SQLite events are 400 sats per GB-month and R240+files are 30 sats per GB-month. The storage path alone does not establish41+which option costs less for a repository.3842 3943 The meters describe application accounting, not a complete provider bill.4044 Awake time is estimated from entrypoint gaps capped at ten seconds, rather41-than timing each job to completion. Direct SQL bookkeeping outside the event42-store is not included in the row meter. Usage is flushed in batches, so it is43-not a durable record of every request. Applications use the reported balance45+than timing each job to completion. Git object reads and writes report their46+SQLite row work to the same meter. Usage is flushed in batches, so it is not a47+durable record of every request. Applications use the reported balance4448 and `outOfFuel` for admission status rather than reconstructing charges from4549 traffic, event counts or wall-clock timings.4650
@@ -51,62 +55,63 @@ traffic, event counts or wall-clock timings.
5155 | Events and Marmot messages | Event storage and database writes; encrypted envelopes still occupy space | Restrict admitted writers and set retention for kinds that do not need permanent history |5256 | Blossom files, dumps and imports | File storage alongside database metadata | Bound uploads and remove stored copies that are no longer needed |5357 | Sites | Manifests occupy event storage; mirrored files occupy file storage; remote fetches and mirrors wake the relay | Reuse unchanged files and choose whether the relay mirrors remote content |54-| Git | Packs, transaction receipts and retained metadata occupy file storage; clone, fetch and push wake the relay | Budget for repository history and ref updates, not just the checked-out files |58+| Git | Repositories occupy SQLite storage as compressed chunks, refs, receipts and indexes; clone, fetch and push wake the relay | Budget for repository history and ref updates, not just the checked-out files |5559 | Pulls, backfills and scheduled dumps | Jobs wake the relay and can add events, rows and files without a connected client | Use once-only jobs for one-time transfers and remove recurring jobs that no longer serve the application |5660 5761 [Relay templates](../relay-templates/README.md) provide starting policies.5862 [Data and names](04-data-and-names.md) describes retention, dumps and jobs.5963 Feature limits still apply below the fuel allowance: a positive balance does60-not increase a maximum upload size, Git transaction limit or repository cap.64+not increase a maximum upload size or repository cap.6165 The limits and admission rules are in6266 [GRASP-01 Git hosting](22-grasp-01-git-hosting.md) and6367 [HTTP reference](14-http-reference.md).6468 6569 ## Account for retained Git storage6670 67-Git storage has no fixed reservation per repository. It grows with packs,68-transaction receipts and metadata, including ref-only updates. Removing a69-branch, hiding an announcement or expiring a PR does not remove its immutable70-history. Retained objects continue to count toward file usage and the relay's71-Git storage cap. There is no automatic Git garbage collector.71+Git storage has no fixed allocation per repository. It grows with compressed72+objects, chunks, refs, transaction receipts and metadata, including ref-only73+updates. Removing a branch, hiding an announcement or expiring a PR does not74+remove its immutable history. Repositories retain these bytes in SQLite75+and they count toward `eventBytes`. There is no automatic Git garbage76+collector.7277 73-A reservation records bytes before an object write. A confirmed result74-reconciles the size; an uncertain result keeps the reservation because the75-write may have succeeded. The reservation total can therefore exceed the76-physical objects currently listed. It is not a second charge on top of those77-objects. Repository format upgrades also retain history; they are not a way78-to reclaim storage.78+Git publication stores compressed object chunks, refs and retry receipts in79+SQLite. Native SQL transactions publish them atomically, so a retry receipt80+and its ref changes commit with the accepted objects. The physical database81+meter includes indexes and metadata as well as compressed chunks. A logical82+Git byte total is diagnostic and is not added to the fuel meter a second time.7983 8084 The relay owner can inspect one hosted repository with the `gitstorage`8185 management method. It takes the repository owner's hex public key and the8286 repository identifier. The repository owner can differ from the relay owner.87+The report identifies the backend and gives compressed payload bytes, raw88+object bytes and metadata bytes for a SQLite repository. Those diagnostics89+explain the SQLite total; they are not additional fuel lines.8390 [Scripts and agents](13-scripts-and-agents.md) describes signing the call;8491 [HTTP reference](14-http-reference.md) defines its responses.8592 8693 | Report field | Interpretation |8794 |---|---|88-| `inventory.listed` | Physical objects found in the repository's storage namespace |89-| `inventory.live` | Objects referenced by the captured storage root, including retained receipts and packs; not a reachability check of every Git object inside a pack |90-| `inventory.unreferenced` | Recognized objects outside that root's dependency set; older readers or unpublished writes can still need them |91-| `inventory.unknown` | Listed keys the inventory cannot classify |92-| `reservations` | The repository's quota reservations, included in file usage |93-94-`reservationMinusListedBytes` compares the reservation and physical byte95-totals. A difference is a diagnostic, not proof that particular reservations96-can be removed. Neither `live` nor `unreferenced` grants deletion authority.97-The report changes no stored objects, reservations or fuel rates.98-99-Inventory is a manual diagnostic, not a health poll. It reads storage and100-holds the relay's admission owner while it runs, so competing Git requests101-and mutations receive a retry response. Server limits bound reads and listing102-work; an incomplete scan returns an error instead of partial totals. The103-60-second cooldown limits repeated scans in a live instance and resets when104-that instance restarts. It is not a recommended polling interval.105-106-`operations` and `inventory.observed` describe the scan's requests and read107-bytes. They help explain the work a scan performs, but are not tenant billing108-meters or a complete hosting cost. Existing activity and storage accounting109-still applies; there is no separate inventory fee.95+| `backend` | `sqlite`, the repository's object and ref backend |96+| `objects` | `count`, `rawBytes`, `compressedBytes` and `metadataBytes` for retained objects |97+| `refs` | Number of stored refs |98+| `receipts` | Number of retained retry receipts |99+| `physicalDatabaseBytes` | The relay's complete SQLite size, including Git, events, indexes and metadata |100+101+The object byte fields explain the repository's logical and compressed102+payloads. `physicalDatabaseBytes` is shared by all relay data and is the value103+used by `eventBytes`; it must not be added to `objects.compressedBytes`. The104+report changes no stored objects, refs, receipts or fuel rates.105+106+Inventory is a manual diagnostic, not a health poll. It reads bounded SQLite107+metadata and holds the relay's admission owner while it runs, so competing Git108+requests and mutations receive a retry response. The 60-second cooldown limits109+repeated scans in a live instance and resets when that instance restarts. It is110+not a recommended polling interval.111+112+`operations` reports zero R2 gets and lists for the SQLite backend. The SQL113+metadata queries still contribute rows to the ordinary SQLite row meter. The114+diagnostic fields are not a second storage charge or a separate inventory fee.110115 111116 ## Handle limits without multiplying work112117
M docs/16-hosting-without-cloudflare.md +1 -0
M docs/22-grasp-01-git-hosting.md +40 -47
@@ -91,7 +91,7 @@ PR must carry the exact signer PR clone URL and a valid repository coordinate
9191 with the same identifier. The signed PR or update and its `c` tip authorize the9292 ref. Unknown events may upload first, but remain hidden until the event is9393 accepted; their deadline is fixed at 20 minutes. Expiry removes refs, while94-immutable WAL packs and orphaned objects remain retained and charged because94+immutable objects and orphaned chunks remain retained and charged because9595 the service has no garbage collector.9696 9797 Promotion checks for relevant pending events before loading Git packs. An
@@ -135,61 +135,54 @@ CORS headers. `OPTIONS` returns 204. GET and POST are the allowed methods;
135135 allowed request headers. The NIP-5A site origins remain isolated from relay136136 events and repository data.137137 138-The Worker depends on the `ntig` package, whose source and package artifact139-are recorded in [Vendored dependencies](../vendor/README.md). Its object-store seam validates pack138+The relay Durable Object's SQLite database stores compressed Git object chunks,139+refs and receipts. The backend removes the former R2 per-push root and140+transaction ceiling while preserving accepted-state authority, retry receipts141+and purgatory behavior. It keeps bounded object, byte, ref and receipt limits,142+and does not promise lower cost for every repository. SQLite storage is charged143+through the existing events allowance. The Worker depends on the `ntig` package, whose source and package artifact144+are recorded in [Vendored dependencies](../vendor/README.md). Its repository contract validates pack140145 structure, deltas, object dependencies, trees and commits before publication.141146 The hard limits are:142147 143148 | Scope | Limit | What it controls |144149 |---|---:|---|145-| Relay | 16 repositories, 320 MiB Git storage | accepted repositories and billed retained Git objects across the relay |146-| Repository | 4 MiB per pack, 16 MiB packed history | one upload and the aggregate packed history read into memory |147-| Repository | 1,024 refs, 4,096 objects | ref maps and verified object graphs |148-| Format 1 | 128 transactions | legacy WAL replay |149-| Format 2 | 128 unique packs, 2 MiB manifest | explicitly migrated repository metadata |150-151-ntig 0.3.0 reads both formats, but installing it leaves existing roots alone152-and new repositories still use format 1. Bindws exposes no checkpoint action153-or scheduled migration. The backend's explicit `checkpoint()` upgrade is154-one-way: every reader and writer must support format 2 before an isolated155-repository is migrated. Versions 0.1.x cannot read the upgraded root.156-Migration retains old data and receipts; it does not reclaim storage. Bulk157-construction publishes the final receipt-index nodes without storing each158-intermediate index from the migration. Existing roots are unchanged.159-160-Format-2 ref advertisements read the root and manifest without downloading161-packs. They retain the same accepted HEAD and hidden PR rules. Full clone,162-fetch and push still load the stored packs; selective object reads,163-compaction, orphan collection, large-pack streaming and production capacity164-measurements remain follow-up work. Once a format-1 repository reaches its165-transaction limit, all further Git writes are refused, including166-the timed cleanup transaction. An expired unknown PR ref therefore stays167-hidden when cleanup cannot commit, while immutable WAL history keeps its old168-object bytes billed until the relay is torn down. The same retention behavior169-applies when a ref is cleaned after its 20-minute window.170-171-Each Git HTTP request reuses immutable object bytes within its existing172-authority fence. The read session retains at most 2 MiB of payload and 512173-entries; larger objects are read normally. Roots are never cached, every174-write still passes through storage reservations, and each new request175-rechecks authority. The session closes on return or error. This bounds the176-cache, not total request memory, and does not skip Git validation or pending177-promotion.150+| Relay | 16 repositories, 320 MiB Git storage | accepted repositories and retained compressed Git objects plus metadata across the relay |151+| Repository | 4 MiB per pack or object | one upload and one stored object |152+| Repository | 16 MiB raw, 16 MiB compressed and 16 MiB metadata | retained object payloads and their SQL records |153+| Repository | 1,024 refs, 4,096 objects and 65,536 graph edges | ref maps and verified object graphs |154+| Repository | receipt metadata within the metadata limit | retry records without a separate transaction-count ceiling |155+156+The SQL object store reads and writes chunks, refs and receipts in native SQL157+transactions. Full clone, fetch and push load bounded Git data, while158+selective object reads use the object index. Compaction, orphan collection,159+large-pack streaming and production capacity measurements remain follow-up160+work. Expired unknown161+PR refs stay hidden when cleanup cannot commit, while immutable object history162+keeps its bytes until the relay is torn down.163+164+Git HTTP walks indexed object metadata to check reachability and filters before165+loading selected object bodies. Every write rechecks authority inside the SQL166+transaction, which publishes objects, refs, receipts and pending-event167+visibility together. The relay admits one repository operation at a time.178168 179169 The owner can request `gitstorage <repository-owner-hex> <identifier>` through180-the management API to compare physical Git objects with quota reservations.181-The report breaks storage down by object class and distinguishes current-root182-references, unreferenced objects and unknown keys. Unreferenced bytes are not183-safe-to-delete bytes: readers and unpublished writes can still need them.184-The scan does not change roots, reservations or stored objects.185-186-Scans run manually with fixed read limits and a 60-second cooldown per live187-relay instance. A restart resets that cooldown. A limit, corrupt dependency or188-changed root returns an error rather than a partial report. Git requests,170+the management API to inspect the repository's object, ref and receipt totals.171+The report includes compressed payload, raw object and metadata bytes plus the172+relay's complete SQLite size. The diagnostic does not change stored objects,173+refs or receipts.174+175+Reports read indexed SQL totals with a 60-second cooldown per live relay176+instance. A restart resets that cooldown. Git requests,189177 event writes and configuration changes receive a retry response while a scan190178 owns the relay. See [HTTP reference](14-http-reference.md) for the method and191179 [Costs and margins](15-costs-and-margins.md) for its host costs and limits.192180 181+Portable relay backups include compressed Git objects, refs and retry receipts182+within the archive's 8 MiB and 12,000-entry limits. Restore verifies repository183+identity, object hashes, dependency graphs and the receipt chain before writing184+the fresh target. See [Data and names](04-data-and-names.md).185+193186 ## Proactive synchronization and archive194187 195188 With `grasp02`, the relay performs repository-scoped historic and bounded live
@@ -256,8 +249,8 @@ origins.
256249 257250 One Git operation runs at a time for a relay. The operation fence also blocks258251 relay-side event and control mutations while a Git transaction reads or259-writes its object graph. This bounds Worker memory and keeps the WAL root and260-the relay's authority view from changing underneath one operation.252+writes its object graph. This bounds Worker memory and keeps the SQL object253+snapshot and the relay's authority view from changing underneath one operation.261254 262255 Git requests consume the relay's normal address rate limits. A busy fence or263256 rate limit answers `429` with a retry reason. Fuel exhaustion, an unclaimed
M src/landing.ts +3 -3
@@ -62,7 +62,7 @@ const MAP = `<svg viewBox="0 0 640 470" role="img" aria-label="how a request rea
6262 <text x="126" y="310">alarm sweep, retention, fuel</text>6363 <text x="126" y="338" class="k">hibernates when idle</text>6464 <path class="e" d="M270 356v26" marker-end="url(#ar)"/>65-<rect class="sh" x="224" y="390" width="100" height="34" rx="10"/><rect class="n" x="220" y="386" width="100" height="34" rx="10"/><text class="k" x="270.0" y="407.0" text-anchor="middle">r2</text><text class="d" x="330" y="407">files, dumps, Git objects</text>65+<rect class="sh" x="224" y="390" width="100" height="34" rx="10"/><rect class="n" x="220" y="386" width="100" height="34" rx="10"/><text class="k" x="270.0" y="407.0" text-anchor="middle">r2</text><text class="d" x="330" y="407">files, dumps</text>6666 <text class="k" x="600" y="266" text-anchor="end">zap</text><text class="d" x="600" y="250" text-anchor="end">kind 9735</text><path class="e" d="M556 262H432" marker-end="url(#ar)"/>6767 <text class="k" x="600" y="330" text-anchor="end">leave</text><text class="d" x="600" y="314" text-anchor="end">nip-77</text><path class="e" d="M432 326H556" marker-end="url(#ar)"/>6868 </svg>`;
@@ -70,7 +70,7 @@ const MAP = `<svg viewBox="0 0 640 470" role="img" aria-label="how a request rea
7070 const NOTES = `<div class="notes">7171 <h3 id="o">durable object</h3><p>Each name has one object. The object holds the SQLite database and the relay's open sockets. The <b>owner</b> is the pubkey that signed the first <b>claim</b>. After the claim, all management is done via NIP-86 calls signed by that key. The relay page is a client of this API. The object can hibernate between frames; retained storage and scheduled work still count.</p>7272 <h3 id="l">lease</h3><p>A lease is a relay at a name picked for you, open to everyone, for a fixed number of days. <b>POST /lease</b> returns one; no key needed. A claim before it expires keeps it, events and files included. After that, the object is wiped and the name is free again.</p>73-<h3 id="f">fuel</h3><p>Fuel prices four values: events stored, files stored, estimated hours awake and metered rows written. Mirrored site files and retained Git data share the file allowance. Traffic is visible but uncharged. Below the allowances, you owe nothing. Above them, a zap adds balance. The Health tab and public <b>/fuel</b> show usage and prices.</p>73+<h3 id="f">fuel</h3><p>Fuel prices four values: events stored, files stored, estimated hours awake and metered rows written. Mirrored site files share the file allowance; Git repositories use SQLite storage and the events allowance. Traffic is visible but uncharged. Below the allowances, you owe nothing. Above them, a zap adds balance. The Health tab and public <b>/fuel</b> show usage and prices.</p>7474 <h3 id="x">leave</h3><p>NIP-77 sync copies readable events to a different relay. Copy site files through Blossom and clone Git repositories separately; event sync does not copy their bytes.</p>7575 </div>`;7676
@@ -156,7 +156,7 @@ export function landing(req: Request, env: Env): Response {
156156 <h2>Limits</h2>${limits}157157 <h2>Metering</h2>${metering}158158 <p class="desc">Below the allowances, you owe nothing. With no balance past an allowance, event writes and uploads stop; GRASP requests and remote site fetches also stop. Fuel exhaustion does not delete stored data.</p>159- <p class="desc">The meters are relay totals, not a bill per feature. Git history remains in file storage after refs expire. Provider requests and object operations are host overhead, not separate tenant charges. <a href="${REPO}/blob/main/docs/02-understanding-fuel.md">Understanding fuel</a> explains the shared allowances and metering limits.</p>159+ <p class="desc">The meters are relay totals, not a bill per feature. Git history remains in retained SQLite storage after refs expire. Provider requests and object operations are host overhead, not separate tenant charges. <a href="${REPO}/blob/main/docs/02-understanding-fuel.md">Understanding fuel</a> explains the shared allowances and metering limits.</p>160160 <h2>Protocol</h2>${protocol}161161 </div>162162 <footer class="foot"><a href="${REPO}">-&gt; github.com/FelineStateMachine/bindws</a><span class="muted">v${VERSION}</span></footer>