clone

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

grasp: one scoped read session spans Git authority and promotion while roots, writes and quota checks stay fresh

Dami committed 44f4aec
commit
44f4aec53de4dd7a6572c0b4b2b16f37e5e7ae38
parent
189a13497910242ea1131a382e5176954f3d5c48
author
Dami <dami.etoile@gmail.com>
5 files changed, +51 -8
Mdocs/15-costs-and-margins.md13+++++++++++--
Mdocs/22-grasp-01-git-hosting.md14++++++++++++--
Msrc/grasp.ts6++++--
Mtest/object/grasp-checkpoint.test.ts15++++++++++++++-
Mtest/object/grasp.test.ts11++++++++++-
M docs/15-costs-and-margins.md +11 -2
@@ -103,7 +103,7 @@ Prices go up when bitcoin falls. When it rises, do nothing until a line has sat
103103 104104 ## Accounting coverage, checked Sept. 4, 2026105105 106-The following inventory describes the host with ntig 0.2.0 and unmigrated106+The following inventory describes the host with ntig 0.2.1 and unmigrated107107 format-1 repositories. Installing the library does not migrate roots. Observing an108108 expense does not authorize a new customer charge. `/fuel` and the Health tab109109 expose aggregate tenant meters, not every operation the host pays for.
@@ -149,6 +149,15 @@ storage. Neither receipt lookup nor checkpointing reclaims old bytes.
149149 Operation counts are host costs, not a new tenant charge; lower read costs150150 alone do not establish a margin after metadata writes and retained storage.151151 152+Git HTTP requests reuse up to 2 MiB and 512 entries of immutable payload in a153+session that ends with the request. Repeated validation and promotion can154+reuse those bytes, while mutable roots and all writes still reach R2 and155+its accounting adapter. New requests start fresh, oversized objects bypass156+the cache, and Git validation still runs. Lower R2 read counts do not imply157+lower CPU cost or a complete request-memory bound. Bulk initial checkpoint158+construction avoids writing intermediate receipt-index nodes; it removes159+no stored data and does not start migration automatically.160+152161 `graspBusy`, `graspControls` and the job round's `working` flag fence work153162 within a live instance. Async handlers can interleave across awaits. Those154163 flags are admission controls, not persisted crash recovery or authority to
@@ -232,7 +241,7 @@ These are proposed operating boundaries, not new allowances or charges.
232241 233242 ## Proposed maintenance contract234243 235-ntig 0.2.0 has no resumable optimization runner. No maintenance job, budget244+ntig 0.2.1 has no resumable optimization runner. No maintenance job, budget236245 setting, wake schedule, format migration or garbage collection is enabled by237246 this contract. Existing ref cleanup, retention and alarms keep their current238247 roles. An eventual integration has these boundaries:
M docs/22-grasp-01-git-hosting.md +12 -2
@@ -130,12 +130,14 @@ The hard limits are:
130130 | Format 1 | 128 transactions | legacy WAL replay |131131 | Format 2 | 128 unique packs, 2 MiB manifest | explicitly migrated repository metadata |132132 133-ntig 0.2.0 reads both formats, but installing it leaves existing roots alone133+ntig 0.2.1 reads both formats, but installing it leaves existing roots alone134134 and new repositories still use format 1. Bindws exposes no checkpoint action135135 or scheduled migration. The backend's explicit `checkpoint()` upgrade is136136 one-way: every reader and writer must support format 2 before an isolated137137 repository is migrated. Versions 0.1.x cannot read the upgraded root.138-Migration retains old data and receipts; it does not reclaim storage.138+Migration retains old data and receipts; it does not reclaim storage. Bulk139+construction publishes the final receipt-index nodes without storing each140+intermediate index from the migration. Existing roots are unchanged.139141 140142 Format-2 ref advertisements read the root and manifest without downloading141143 packs. They retain the same accepted HEAD and hidden PR rules. Full clone,
@@ -148,6 +150,14 @@ hidden when cleanup cannot commit, while immutable WAL history keeps its old
148150 object bytes billed until the relay is torn down. The same retention behavior149151 applies when a ref is cleaned after its 20-minute window.150152 153+Each Git HTTP request reuses immutable object bytes within its existing154+authority fence. The read session retains at most 2 MiB of payload and 512155+entries; larger objects are read normally. Roots are never cached, every156+write still passes through storage reservations, and each new request157+rechecks authority. The session closes on return or error. This bounds the158+cache, not total request memory, and does not skip Git validation or pending159+promotion.160+151161 ## Information document and limits152162 153163 When GRASP is on and reads are open, NIP-11 includes:
M src/grasp.ts +4 -2
M test/object/grasp-checkpoint.test.ts +14 -1
@@ -107,6 +107,7 @@ describe("GRASP checkpoint integration", () => {
107107 beforeBytes: measurements.before.bytes,108108 afterBytes: measurements.after.bytes,109109 retainedKeys: measurements.keyCount,110+ categories: measurements.categories,110111 sequence: measurements.sequence,111112 });112113 // The accounting adapter retains every immutable manifest/index/receipt
@@ -128,6 +129,7 @@ describe("GRASP checkpoint integration", () => {
128129 const operations = { get: 0, getBytes: 0, put: 0, putBytes: 0, putAck: 0, head: 0, headBytes: 0 };129130 let writes = 0;130131 let throwManifest = false;132+ let ownerCap = 0;131133 let failedManifestKey = "";132134 let failedManifestBytes = 0;133135 const previous = { ...operations };
@@ -172,7 +174,7 @@ describe("GRASP checkpoint integration", () => {
172174 media: bucket,173175 sql: relay.sql,174176 fuelStatus: () => ({ outOfFuel: false }),175- settings: { limitsOf: () => ({ cap: 0 }) },177+ settings: { limitsOf: () => ({ cap: ownerCap }) },176178 store: { authorBytes: () => 0 },177179 } as unknown as typeof relay;178180 const repo = { id: "a".repeat(64), owner: pk(owner), identifier: "ambiguous", clone: [], relays: [], maintainers: [pk(owner)], private: false };
@@ -203,6 +205,17 @@ describe("GRASP checkpoint integration", () => {
203205 console.info("ambiguous checkpoint reservation", reserved);204206 expect(reserved.n).toBeGreaterThan(0);205207 expect(reserved.bytes).toBeGreaterThan(0);208+ ownerCap = relay.sql.exec<{ bytes: number }>("SELECT sum(size) AS bytes FROM grasp_objects WHERE owner=?", pk(owner)).one().bytes;209+ const beforeQuota = await wal.load();210+ const putsBeforeQuota = operations.put;211+ await wal.withReadSession(async (scoped) => {212+ await expect(scoped.commit({ id: "ambiguous-initial", updates: [{ name: "refs/heads/main", old: null, new: fixture.commitID }], pack: fixture.pack })).resolves.toMatchObject({ sequence: 1, replayed: true });213+ await expect(scoped.commit({ id: "over-quota", updates: [{ name: "refs/tags/quota", old: null, new: fixture.commitID }] })).rejects.toThrow("Repository owner's storage cap reached");214+ });215+ expect(operations.put).toBe(putsBeforeQuota);216+ expect((await wal.load()).sequence).toBe(beforeQuota.sequence);217+ expect((await wal.loadRefs()).refs["refs/tags/quota"]).toBeUndefined();218+ expect(relay.sql.exec<{ bytes: number }>("SELECT sum(size) AS bytes FROM grasp_objects WHERE owner=?", pk(owner)).one().bytes).toBe(ownerCap);206219 });207220 });208221 });
M test/object/grasp.test.ts +10 -1