g1t/docs/ARTIFACTS.md

523 lines47,655 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1# Cloudflare Artifacts: due diligence for g1t at launch scale
2
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily3Status: research document, 2026-10-06; R1–R5, R9, R10, R13 and R7 groundwork were built the same day (section 9).
Fast pages, required checks on the branch, self-hosted runners, honest incidents4Scope: everything g1t stores in Cloudflare Artifacts (open beta since 2026-10-01; billing from 2026-10-14),
5measured against what Cloudflare documents, and what we must build so that a few thousand workspaces
6can run on it.
7
8## Summary
9
10Artifacts fits what g1t does: one Durable Object per repository, smart HTTP for clones and pushes, a
11read-only binding for commits, trees and blobs, forks and short-lived tokens. Nothing we rely on is
12missing outright. Five things are not yet safe at a few thousand workspaces.
13
141. **We do not know what an "operation" is.** Pricing says "repo operations, such as `create`, `push`,
15 `pull`, and `clone`". Metrics list only `create`, `fork`, `push`, `pull`, `delete`. If binding reads
16 (`get`, `info`, `createToken`, `log`, `readTree`, `readBlob`, `readCommit`, `readFile`) and every git
17 HTTP request also count, our bill at launch scale is roughly **15× larger** (about $31k a month instead
18 of about $1.8k). We must settle this before 2026-10-14.
192. **Pull request forks are never deleted.** Every pull request is an Artifacts fork (`pulls--<id>`). They
20 are purged only with their parent repository. Cloudflare does not say whether a fork shares objects
21 with its source. If forks copy objects, an agent-heavy workload reaches the **1 TB account storage
22 limit in days**, and every push then fails. If they share, storage still grows without bound.
233. **One namespace carries everything.** All repositories and forks live in the `g1t` namespace. The
24 control-plane limit is **2,000 requests per 10 seconds per namespace** (200 per second). If binding
25 calls count against it, page views, token mints and mergeability checks together exceed it at launch
26 peaks.
274. **Hard limits are not enforced in front of Artifacts.** 1 GB per repository, 32 MB per file, and a
28 128 MB Worker isolate that buffers each push body twice. Large pushes and imports fail late, without a
29 message git can show.
305. **No backup, no exit drill.** Cloudflare replicates data, but there is no SLA, no documented export
31 besides git itself, and the self-host git store is not a production fallback yet.
32
33None of these blocks an invite-only launch. Items 1 and 2 must be answered before billing starts on
342026-10-14, and the fork cleanup must ship before agent pull requests reach thousands a day.
35
36## 1. What Cloudflare documents
37
38All quotes are from developers.cloudflare.com, retrieved 2026-10-06.
39
40### Limits ([Artifacts limits](https://developers.cloudflare.com/artifacts/platform/limits/), updated 2026-10-01)
41
42| Limit | Value |
43| --- | --- |
44| Control-plane request rate | 2,000 requests per 10 seconds **per namespace** |
45| Git request rate | 2,000 requests per 10 seconds **per repository** |
46| Storage per repository | **1 GB** |
47| Largest file or blob | **32 MB** |
48| Storage per account | **1 TB** (can be raised on request) |
49| Repositories, namespaces | Unlimited |
50| Names | 2 to 63 characters; letters, digits, `.`, `_`, `-`; start with a letter or digit |
51
52### Pricing ([Artifacts pricing](https://developers.cloudflare.com/artifacts/platform/pricing/))
53
54- Workers Paid only. "Cloudflare will begin billing for Artifacts operations and storage on October 14, 2026."
55- Operations: first 10,000 a month, then **$0.15 per 1,000**. Defined only as "the number of repo
56 operations, such as `create`, `push`, `pull`, and `clone`."
57- Storage: first 1 GB, then **$0.50 per GB-month**, "calculated by averaging peak storage per day over a
58 30-day billing period". "Replicas do not add storage charges." "Repos remain stored until you
59 explicitly delete them."
60
61### Architecture ([announcement blog](https://blog.cloudflare.com/artifacts-git-for-agents-beta/), 2026-04-16, Matt Carey and Matt Silverlock)
62
63- Each repository is a Durable Object; the git server is Zig compiled to WebAssembly (about 100 KB).
64- "Files are stored in the underlying Durable Object's SQLite database." "Durable Object storage has a
65 2MB max row size, so large Git objects are chunked and stored across multiple rows."
66- "DOs have ~128MB memory limits." Fetch and push stream (`ReadableStream<Uint8Array>`).
67- "Artifacts also uses R2 (for snapshots) and KV (for tracking auth tokens)."
68- Deltas are stored as received; "if the requesting client already has the base object, Zig emits the
69 delta instead."
70- Durability ([How Artifacts works](https://developers.cloudflare.com/artifacts/concepts/how-artifacts-works/)):
71 "Cloudflare replicates repo data synchronously across multiple data centers and copies it
72 asynchronously to object storage and snapshots."
73- A repository is "a single logical instance that Cloudflare can route to from any region", like a
74 Durable Object. Durable Objects have a soft limit of 1,000 requests per second each
75 ([DO limits](https://developers.cloudflare.com/durable-objects/platform/limits/)).
76
77### Git protocol ([Git protocol](https://developers.cloudflare.com/artifacts/api/git-protocol/))
78
79| Feature | Documented support |
80| --- | --- |
81| Clone and fetch | Protocol v1 and v2 (`ls-refs`, `fetch`); v1 shallow and deepen |
82| Push | v1 receive-pack only; v2 receive-pack not supported |
83| `filter` (partial clone), `include-tag` | "not supported" for v1 |
84| Tokens | `art_v1_<40 hex>?expires=<unix seconds>`; scopes `read`, `write`; TTL 60 s to 1 year, default 24 h |
85
86**Measured differently:** a protocol v2 `git clone --filter=blob:none` of `flagon-io/g1t` through g1t
87returned a real partial clone (2,789 objects, 903 KB) instead of the full 6,187 objects and 5.5 MB. So
88v2 filtering works today, despite the table. Ask Cloudflare whether this is supported or accidental.
89
90### Binding API ([Workers binding](https://developers.cloudflare.com/artifacts/api/workers-binding/); generated types in `services/*/worker-configuration.d.ts`)
91
92- Namespace: `create`, `get`, `list`, `import`, `delete`. `get()` is a lookup that throws `NOT_FOUND` or
93 `*_IN_PROGRESS`, so it costs a round trip.
94- Repository handle: `info()` ("Each call performs a fresh lookup"), `createToken`, `listTokens`,
95 `revokeToken`, `log` (first-parent only, at most 1,000), `readCommit`, `readTree` (one level),
96 `readBlob`, `readFile`, `fork` (`defaultBranchOnly` defaults to true).
97- Not available: listing refs, updating refs, writing objects, repository size, gc or repack, hooks.
98 g1t works around the first three over smart HTTP (`refs.rs`, `land.rs`, `catch_up.rs`).
99- Errors: `MEMORY_LIMIT` (10402) "if the object cannot be buffered safely", `INTERNAL_ERROR` (10400),
100 `UPSTREAM_UNAVAILABLE`, `IMPORT_IN_PROGRESS`, `FORK_IN_PROGRESS`, `CREATE_IN_PROGRESS`
101 ([Errors](https://developers.cloudflare.com/artifacts/api/errors/)).
102- Doc drift: the [isomorphic-git example](https://developers.cloudflare.com/artifacts/examples/isomorphic-git/)
103 still says the binding "cannot read or write files inside them". It can read; it cannot write.
104
105### Observability, events, data location
106
107- Metrics: GraphQL dataset `artifactsEventsAdaptiveGroups`, 31 days, with `eventType` in `create`,
108 `fork`, `push`, `pull`, `delete`, and errors `storageLimitReached`, `serverError`, `clientError`,
109 `rateLimited` ([Metrics](https://developers.cloudflare.com/artifacts/observability/metrics/)).
110 **This is the fastest way to learn what Cloudflare counts.**
111- Events: account level `repo.created|deleted|forked|imported`; per-repository `pushed`, `cloned`,
112 `fetched`, `token.created|revoked`, which need one subscription per repository
113 ([Event subscriptions](https://developers.cloudflare.com/artifacts/guides/event-subscriptions/)).
114 g1t reports pushes itself for that reason (`record_push`).
115- Jurisdictions `eu` and `us`, chosen when a namespace is created and never changeable
116 ([Data localization](https://developers.cloudflare.com/artifacts/guides/data-localization/)). A binding
117 names one namespace, so EU residency needs a second binding.
118- Best practices: "Do not keep every repo in one default namespace once usage grows" and "When one
119 namespace becomes hot, shard new repos into additional namespaces"
120 ([Best practices](https://developers.cloudflare.com/artifacts/concepts/best-practices/)).
121- Beta: the docs state no SLA, no support tier, and no backup or export feature beyond git itself.
122
123### Platform limits that bound us
124
125| Limit | Value | Source |
126| --- | --- | --- |
127| Worker memory | 128 MB per isolate, shared by concurrent requests | [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) |
128| Request body | 100 MB on Free and Pro zones, 200 MB Business, up to 5 GB Enterprise | same |
129| Response body | No limit (CDN cache 512 MB) | same |
130| CPU | 30 s default, up to 5 min | same |
131| Subrequests | 10,000 per invocation (Paid); 6 connections waiting for headers at once | same |
132| Cache API | Per data center only; calls share the subrequest quota | [Cache](https://developers.cloudflare.com/workers/runtime-apis/cache/) |
133| KV | 1 write per second per key; 25 MiB values; 60 s minimum TTL | [KV limits](https://developers.cloudflare.com/kv/platform/limits/) |
134| D1 | 10 GB per database; 30 s per query; one writer | [D1 limits](https://developers.cloudflare.com/d1/platform/limits/) |
135| Cloudflare REST API | 1,200 requests per 5 minutes per user | [API rate limits](https://developers.cloudflare.com/fundamentals/api/reference/limits/) |
136| Containers | Up to 4 vCPU, 12 GiB, 20 GB disk (ephemeral) | [Containers limits](https://developers.cloudflare.com/containers/platform-details/limits/) |
137| Smart Placement | Applies to fetch handlers, not RPC entrypoints | [Placement](https://developers.cloudflare.com/workers/configuration/placement/) |
138
139The REST API's 1,200 requests per 5 minutes means the hot path must use the binding, never the REST API.
140
141## 2. How g1t uses Artifacts
142
143All Artifacts traffic goes through `services/repos` (`g1t-repos`), binding `ARTIFACTS`, namespace `g1t`,
144placement off. Store keys are `<workspace>--<repo>`; pull request forks are `pulls--<pull id>`.
145
146### Calls per user action
147
148"Binding" counts calls on `env.ARTIFACTS`; "git" counts HTTP requests to the repository's remote.
149
150| Action | Path in code | Binding calls | Git requests to Artifacts |
151| --- | --- | --- | --- |
152| Credential (mint) | `store.rs` `mint_access` | 3: `get`, then `info` and `createToken` at once | 0 |
153| Clone, protocol v2 | `lib.rs` `answer_git` → `git_http::forward` | mint if none kept (3) | `info/refs` and `ls-refs` (0 on a `refs_cache` hit), `fetch` 1 |
154| Clone, protocol v0 | same | mint if none kept | `info/refs` (0 on hit), `upload-pack` 1 |
155| Fetch, nothing new | same | as above | `ls-refs` (0 on hit), `fetch` 1 |
156| Push | same, then `record_push` | mint; then `get` + `log(branch, 1)` per pushed branch | `info/refs` 1, `receive-pack` 1 |
157| Tree page | `Repos::tree` | `get`, `log(ref, 1)`, `readTree` per path segment (Cache API), `readBlob` README (cached) | 0 |
158| File page | `Repos::blob` | `get`, `readFile` (not cached) | 0 |
159| Branches list | `GitRepo::branches` (`refs.rs`) | mint (3) | `info/refs` 1 (does not use `refs_cache`) |
160| Blame | `blame.rs` | `log(ref, 400)`, then trees and blobs per commit | 0 (one view took 6.6 s wall in the tail) |
161| Open a pull request | `fork_for_pull` | `get`, `fork` | 0 |
162| Mergeability (each time the target branch moves, for up to 100 open pull requests) | `divergence` | `get` ×2, `log(…, 1000)` ×2, `readCommit` per commit off the first-parent chain, `readTree` per changed directory | 0 |
163| Catch-up (no conflicts) | `catch_up.rs` | `get` ×2, `log` ×2, trees | `upload-pack` 1, `receive-pack` 1 |
164| Land a pull request | `land.rs` | 2 mints (6) | `upload-pack` 1 from the fork, `receive-pack` 1 to the target |
165| Commit one file | `commit_file.rs` | mint, `log` | `info/refs` 1, `receive-pack` 1 |
166| Mirror sync or import | `mirror.rs`, `import.rs` | mint | `info/refs` 1–2, `upload-pack` and `receive-pack` 1 each; packs capped at 40 MB |
167| Search indexing | `listing.rs` | trees by level, blobs in groups | 0 |
168| Push protection | `secret_scan.rs` | trees and blobs for bases, up to 24 MB scanned | 0 |
169| Delete (purge) | `lifecycle.rs` | `delete` per key, including forks | 0 |
170
171Every sandbox job clones in full (`crates/runner/src/{main,checks,review,plan,queue,reply,update,deploy,mergecheck}.rs`:
172`git clone --quiet`, no depth, no filter), and most then fetch an upstream branch. Only Actions'
173checkout fetches with `--depth=1` (`crates/runner/src/actions/uses.rs`).
174
175### What already saves calls
176
177| Mitigation | Where | What it saves |
178| --- | --- | --- |
179| Credential cache | `store.rs` `Credentials` (per isolate) and `shared.rs` (KV, sealed), TTL 300 s, reused 180 s | 3 binding calls and about 800 ms per git request on a hit |
180| Ref listing cache | `refs_cache.rs`, keyed by `refs_version`, 60 s TTL, colo Cache API then KV | the `info/refs` and `ls-refs` round trip (330–400 ms) on a hit; measured hits answer in 3–6 ms |
181| Object cache | `store.rs` `cached`/`keep`, Cache API, immutable | `readTree`, `readBlob` (≤ 1 MB), and `log` by commit hash |
182| Recent row cache | `registry.by_path_recent` | D1, not Artifacts |
183| Soft delete | `lifecycle.rs` | an accidental `delete` cannot lose data for `RESTORE_DAYS` |
184
185Gaps in those caches: the Cache API is per data center, so each colo pays its own misses; `log` by branch
186name, `readFile`, `branches()` and `readCommit` are never cached; the credential reuse window is 180 s,
187so a busy repository mints about 20 times an hour per scope.
188
189## 3. Measured in production
190
191Read-only, from a client in Denver (`CF-Ray …-DEN`), 2026-10-06 07:40 UTC, git 2.45 user agent.
192`flagon-io/hello`: 239 objects, 44 KB pack. `flagon-io/g1t`: 6,187 objects, 5.5 MB pack.
193
194| Measurement | hello | g1t |
195| --- | --- | --- |
196| `info/refs`, cold: credential minted | n/a | 1,306–1,425 ms total: `mint` 807–856 ms, `store` 334–399 ms |
197| `info/refs`, `refs_cache` hit | 3–6 ms server, 80–150 ms client | 3–6 ms server |
198| `ls-refs` / v2 advertisement miss with kept credential | 436 ms (`store` 349) | 458 ms (`store` 332) |
199| Full upload-pack (replayed v0 want, kept credential) | 674–1,104 ms, `store` 607–997 | 1,960–2,223 ms to first byte, `store` 1,855–2,156; 3.2 s total |
200| `git clone --bare`, first / repeats | 5.1 s / 2.0–2.4 s | 5.4 s / 4.2 s (4.8–5.8 s during the tail) |
201| `git fetch`, nothing new | 0.99 s first, then 0.39–0.42 s | 0.95 s first, then 0.35–0.41 s |
202| `clone --depth=1` | 1.6 s | 3.8 s |
203| `clone --filter=blob:none` | 1.4 s | 3.4 s, 903 KB |
204| `kept` step on every POST | 63–98 ms | same |
205
206Observations:
207
208- Artifacts builds the whole pack before the first byte: about 2 s for 5.5 MB. Every full clone pays
209 this, every time, because nothing caches packs.
210- Minting a credential costs about 0.8 s, almost all in `get` then `info` and `createToken`.
211- The 63–98 ms `kept` step on every POST is not Artifacts. It is `git_limits` doing a D1 upsert into
212 `git_operations` before the request is forwarded.
213- `wrangler tail g1t-repos` for 75 s: 87 events, all `ok`, no exceptions or error logs. The slowest were a
214 blame RPC (6.6 s wall, 178 ms CPU) and an `info/refs` miss with a mint (2.6 s).
215
216## 4. Where we and the docs disagree
217
218| # | Topic | Documented | What g1t does or assumes | Risk | Fix |
219| --- | --- | --- | --- | --- | --- |
220| M1 | What an operation is | "create, push, pull, clone"; metrics events `create`, `fork`, `push`, `pull`, `delete` | `git_ops.rs` bills workspaces per upload-pack or receive-pack POST, including `ls-refs` answered from our cache; binding reads assumed free | Cost could be about 15× the plan; customers billed for operations that never reach Artifacts | R1 |
221| M2 | Fork storage | Not documented; fork returns `objects: N` | `store.rs` calls forks "copy-on-write"; forks never deleted until the parent is purged | 1 TB account limit; storage grows forever | R2 |
222| M3 | Namespace rate | 2,000 per 10 s per namespace | One namespace for all repositories and forks | 429s across all of g1t at peak | R7 |
223| M4 | Repository size | 1 GB | Free private storage is 1 GB per workspace; no per-repository check; imports capped at 40 MB | Late `storageLimitReached` failures with no explanation | R4 |
224| M5 | File size | 32 MB | No check before forwarding | Push fails inside Artifacts | R4 |
225| M6 | Push bodies | Workers 128 MB per isolate; 100 MB body cap on our zone plan | `git_http::forward` reads the body (`request.bytes()`) and copies it into a `Uint8Array`; push protection also reads it | Pushes above roughly 40–50 MB can exceed isolate memory, taking concurrent requests with them | R4 |
226| M7 | Landing and mirrors | Fetch streams | `land::fetch_pack` buffers the pack, `unpack_sideband` copies it, `push_pack` copies again | Large pull requests can fail to land | R4 |
227| M8 | Token lifetime | 60 s to 1 year | 300 s, reused 180 s | Mint load and 0.8 s latency on each miss | R3 |
228| M9 | `info()` | "Each call performs a fresh lookup" | Called on every mint only to read `remote` | One wasted call per mint; `remote` is fixed per key | R3 |
229| M10 | Partial clone | v1 `filter` unsupported | v2 `blob:none` works in production | Building on undocumented behaviour | Q3 |
230| M11 | Read-after-write | Not documented | `record_push` reads `log(branch, 1)` right after the push response; `refs_moved` assumes refs are final when the response ends | Missed push events if a read lags | Q5 |
231| M12 | Errors | Typed `ArtifactsError`, `rateLimited` events | `ALREADY_EXISTS`/`NOT_FOUND` tolerated; everything else returns 500; no retry, no breaker | Brief Artifacts errors become user-visible failures | R5 |
232| M13 | Durability | Synchronous replication, asynchronous snapshots; no SLA; no export | No copy outside Artifacts | Beta incident or account issue with no recovery path | R11 |
233| M14 | Data location | Jurisdiction per namespace, fixed | `PLAN.md` promises residency per workspace; only `g1t` exists | EU customers cannot be offered residency | R7 |
234| M15 | Direct credentials | Tokens are bearer, repo-scoped | `git_access` hands out raw write tokens; pushes with them skip branch protection and push protection (`refs_open` only stops caching) | Policy bypass if a token leaks out of a sandbox | Keep TTL minimal on this path |
235| M16 | Hot repository | 2,000 git requests per 10 s per repository; DO soft limit 1,000 req/s | Agents clone the same repository many times per pull request | Not near the limit today; a monorepo with many agents could be | R6 |
236
237## 5. Capacity model: 3,000 workspaces
238
239### Assumptions
240
241- 3,000 workspaces, 10,000 repositories, 6,000 active people, average repository 25 MB stored.
242- People: 6 fetches and 3 pushes per person per day.
243- Agent pull requests: 15,000 a day (5 per workspace). Human pull requests: 3,000 a day.
244- Per agent pull request: 1 fork; 1.5 agent runs × (clone + upstream fetch + push); 1.5 check clones;
245 review clone + fetch; mergecheck in 30% of cases (clone + fetch); 0.5 catch-up push; merge queue clone +
246 fetch; landing upload-pack + receive-pack; 0.3 deploy clones. About **14 git operations**.
247- Actions: 1.5 checkouts per push. Mirrors: 1,000 repositories × 24 syncs × 2.
248- Web: 60 page views per person per day, about 5 uncached binding calls each.
249- Mergeability: 18,000 default-branch moves a day × 8 open pull requests × about 20 binding calls.
250
251### Operations per day
252
253| Source | Scenario A: git data operations only | Scenario B: every Artifacts call |
254| --- | ---: | ---: |
255| People: fetch and push | 54,000 | 54,000 |
256| Agent pull requests (15,000 × 14) | 210,000 | 210,000 |
257| Human pull requests (3,000 × 8) | 24,000 | 24,000 |
258| Actions checkouts | 60,000 | 60,000 |
259| Mirrors | 48,000 | 48,000 |
260| Credential mints (≈ 200,000 × 3) | 0 | 600,000 |
261| Ref listing misses (`info/refs`, `ls-refs`) | 0 | 300,000 |
262| Web pages (360,000 × 5) | 0 | 1,800,000 |
263| Mergeability (18,000 × 8 × 20) | 0 | 2,900,000 |
264| Search indexing, push protection, `record_push` | 0 | 900,000 |
265| **Total per day** | **≈ 400,000** | **≈ 6,900,000** |
266| **Per month** | **≈ 12 M** | **≈ 207 M** |
267| **Cost per month at $0.15 / 1,000** | **≈ $1,800** | **≈ $31,000** |
268
269Rates: scenario A averages 5 operations per second. Scenario B averages 80 calls per second and peaks
270around 250–400, above the 200 per second a single namespace allows, if binding calls count toward it.
271
272Hot spots, largest first in scenario B: mergeability fan-out (42%), web reads (26%), credential mints
273(9%), indexing and scanning (13%), sandbox clones (6%, but they carry almost all the bytes and the
274latency).
275
276### Storage
277
278| Item | Estimate | Cost per month |
279| --- | --- | ---: |
280| 10,000 repositories × 25 MB | 250 GB | ≈ $125 |
281| Pull request forks if shared (about 0.5 MB of new objects each) | +270 GB every month, never deleted | +$135, growing each month |
282| Pull request forks if copied (25 MB each, 540,000 a month) | +13.5 TB a month | ≈ $6,750 the first month, and the 1 TB account limit is reached in about 2 days |
283
284### Free tier exposure
285
286`GIT_OPERATIONS_FREE_CAP` is 50,000 operations per workspace per month, never charged. At $0.15 per 1,000
287that is at most $7.50 per free workspace, or $22,500 a month if all 3,000 used it. Realistic use is far
288lower, but the cap should be sized to what Cloudflare actually counts (R1).
289
290## 6. Recommendations, ranked by risk × effort
291
292P0 means before 2026-10-14 or before agent volume ramps. Effort assumes one engineer.
293
294| # | Priority | What to build | Where | Expected effect | Effort |
295| --- | --- | --- | --- | --- | --- |
296| R1 | P0 | Find out what counts. Query `artifactsEventsAdaptiveGroups` grouped by `eventType` for the last 31 days and compare with `git_operations`; ask Cloudflare in writing. Then make `git_ops::count` count what Cloudflare counts (skip `ls-refs` and cache hits if they are free). | `services/repos/src/git_ops.rs`, a script under `scripts/` | Removes the 15× uncertainty; billing matches cost | 0.5 day plus Cloudflare's answer |
297| R2 | P0 | Delete pull request forks after merge or close plus a grace period (for example 14 days), and verify fork storage semantics with a 100 MB test repository and the storage metric. | `lifecycle.rs` sweep (`23 * * * *`), `work` events `pull.merged`/`pull.closed` | Storage proportional to open work instead of all history; removes the 1 TB cliff | 1–2 days |
298| R3 | P0 | Mint less and faster: TTL 3,600 s with a 50 min reuse window for credentials that never leave the service; keep 300 s for `git_access`. Derive `remote` from the key (`https://<account>.artifacts.cloudflare.net/git/g1t/<key>.git`) or keep it in the registry `store` column instead of calling `info()`. Retry a 401 once with a fresh token (already done for GET). | `store.rs` `TOKEN_TTL_SECONDS`, `TOKEN_REUSE_MS`, `ArtifactsRepo::access` | Mints about 15× fewer; cold git requests about 0.5 s faster | 0.5 day |
299| R4 | P0 | Enforce limits in front of Artifacts: refuse objects over 32 MB and pushes that would take a repository over about 950 MB, with a git `ng` line (same framing as `declined`); stream the push body to Artifacts instead of buffering it twice, keeping only the command section and at most `MAX_SCANNED_PUSH` for scanning; stream `land` packs straight from upload-pack into receive-pack. | `git_http.rs` `forward`, `secret_scan.rs`, `land.rs` | Clear errors instead of late failures; no isolate OOM on 50–100 MB pushes | 2–3 days |
300| R5 | P0 | Resilience: map `ArtifactsError.code` and HTTP 429/5xx to retry with jittered backoff (reads, mints, ref listings only; never a receive-pack), a per-isolate circuit breaker, `Retry-After` to git, and an "Artifacts" component on status.g1t.sh fed by the GraphQL error metrics (`rateLimited`, `serverError`, `storageLimitReached`). | `store.rs`, `git_http.rs`, `apps/status` | Brief Artifacts errors stop reaching users; incidents are visible | 2 days |
301| R6 | P1 | Pack cache for full clones: when an upload-pack request has wants and no haves, key it by (repo id, `refs_version`, hash of the request) and serve the pack from R2; fill it on a miss with a tee of the response. Sandboxes clone the same commit repeatedly per pull request. | `git_http.rs`, `refs_cache.rs` pattern, new R2 bucket | Removes about 2 s of pack building per repeat clone and a large share of `pull` operations | 3–4 days |
302| R7 | P1 | Shard namespaces: bindings `ARTIFACTS_0…N` plus `ARTIFACTS_EU`; record each repository's namespace in the registry `store` column (already read by `remember_store`); new repositories and forks go to the least-loaded shard; forks in their own namespaces. | `services/repos/wrangler.jsonc`, `store.rs`, `registry.rs` | Multiplies the control-plane ceiling; enables EU residency | 3 days |
303| R8 | P1 | Sandboxes clone less: `--depth=1` for checks and deploy; `--filter=blob:none` only where lazy blob fetches are few (review of a small diff); keep full clones for agent runs and merges. Or restore a cached sandbox snapshot and `git fetch` (Sandbox SDK backups). | `crates/runner/src/*.rs` clone calls | Measured: 3.8 s (depth 1) and 3.4 s (blobless) against 5.4 s for g1t; fewer bytes from Artifacts | 1–2 days |
304| R9 | P1 | Cache mutable reads by `refs_version`: `log(branch, n)`, `branches()` (parse the kept `refs_cache` advertisement instead of a new `info/refs`), and `readFile` (resolve the path through cached trees, then a cached `readBlob`). | `store.rs`, `refs.rs`, `lib.rs` `tree`/`blob`/`branches` | Most web page views stop reaching Artifacts | 2 days |
305| R10 | P1 | Calm mergeability: coalesce re-checks per repository (one pass per target head, at most once a minute), resolve the target head once and `log` by hash (already cached), and cache `readCommit` like other objects. | `services/work/src/mergeability.rs`, `lib.rs` `divergence`, `descends_from` | The largest scenario-B hot spot drops by an order of magnitude | 1–2 days |
306| R11 | P2 | Back up every repository to R2: a nightly incremental `git bundle` per repository whose `refs_version` changed, made in a Container (Workers cannot run git), with a restore drill. | new job in `services/runner` or a Container | Durability independent of the beta; also the export path | 3–5 days |
307| R12 | P2 | Make the git store a real fallback: run `deploy/self-host/gitstore` on a host with persistent disk (Containers' disk is ephemeral and at most 20 GB), restore from the R2 bundles, and switch `GitStore` per namespace shard. | `deploy/self-host/gitstore`, `store.rs` | A tested exit path; not a hot standby | 1–2 weeks |
308| R13 | P2 | Take the operation counter off the request path: record the count in `waitUntil` (or Analytics Engine), and check limits against a value cached for a minute. | `lib.rs` `git_limits`, `git_ops.rs` | Saves 63–98 ms on every fetch and push; removes a hot D1 row per workspace-hour | 1 day |
309| R14 | P2 | Try Smart Placement or a placement hint for `g1t-repos`, which makes several sequential calls to Artifacts per request; compare `Server-Timing` before and after. | `services/repos/wrangler.jsonc` | Possibly lower latency for distant users | 0.5 day |
310| R15 | P3 | Large files: an LFS endpoint backed by R2 for files over 32 MB. | new route in `services/repos` | Repositories with binaries can move to g1t | 1–2 weeks |
311
312## 7. Questions for Cloudflare
313
314Ask in the Artifacts beta channel (the [beta request form](https://forms.gle/DwBoPRa3CWQ8ajFp7) is the
315listed contact), through our account team, and in the Cloudflare Developers Discord. As a git
316competition entrant (due 2026-10-14) we can also ask the competition organisers. Get answers in writing.
317
3181. Exactly what is billed as an operation? Is each `upload-pack` and `receive-pack` request one, or each
319 clone or fetch? Do `info/refs`, `ls-refs`, binding calls (`get`, `info`, `createToken`, `log`,
320 `readTree`, `readBlob`, `readCommit`, `readFile`) or token creation count?
3212. Does `fork` share objects with its source or copy them? How is a fork's storage billed? Does
322 `objects` in the fork response mean objects copied?
3233. Is protocol v2 `filter` (partial clone) supported, given it works today but the docs say it does not?
3244. Do binding calls count toward "2,000 requests per 10 seconds per namespace"? What happens past it
325 (429, `rateLimited`, queueing)? Can it be raised per namespace?
3265. Read-after-write: once a receive-pack response has ended, are refs visible to `log`, `info/refs` and
327 other locations at once?
3286. Is `receive-pack` with `atomic` supported for multi-ref pushes?
3297. Where does a repository's Durable Object live, can it move, and can we hint a region per repository?
3308. What SLA, support path and incident communication apply during open beta? When is GA?
3319. Is there an export or snapshot restore for a deleted or corrupted repository? How long are R2
332 snapshots kept?
33310. Can the 1 GB per repository and 32 MB per file limits be raised, and what does a client see when a
334 push crosses them?
33511. Does Artifacts garbage-collect or repack? Do deleted branches and force pushes free storage, and when?
33612. Can the 1 TB account limit be raised now, ahead of launch?
33713. Are per-repository event subscriptions practical at tens of thousands of repositories, or is an
338 account-level `pushed` event planned?
339
340## 8. Launch blockers
341
342- **Before 2026-10-14:** R1 (know what is billed and count the same thing) and R2's verification (fork
343 storage semantics). If forks copy objects, R2's deletion is a launch blocker for agent-heavy
344 workspaces, because the 1 TB account limit would stop every push.
345- **Before agent volume ramps (about 1,000 pull requests a day):** R2 deletion, R3, R4, R5.
346- **Before a few thousand active workspaces:** R6, R7, R9, R10.
347- Everything else is resilience and exit planning, ideally done while the product is still invite-only.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily348
349## 9. What was built (2026-10-06)
350
351Code in `services/repos` unless named; one migration,
352`migrations/0011_artifacts_meters_forks_health.sql` (new columns on `repos`, new tables
353`artifacts_meters`, `operation_mapping`, `store_health`; additive, no backfill).
354
355| # | Status | What |
356| --- | --- | --- |
357| R1 | Built; Cloudflare's answer still needed | Every interaction with the store is metered raw (`meters.rs` → `artifacts_meters`, per day, namespace, repository, workspace and meter, with bytes where known): client git (`git.info_refs`, `git.ls_refs`, `git.fetch`, `git.receive_pack`), g1t's own git (`internal.git.*`: landing, catch-up, mirrors, branch listings, fork retirement), every binding call (`binding.get`, `binding.create_token`, `binding.log`, `binding.read_tree`, …, each retry included), and answers g1t served from its own cache (`cache.*`, never operations). Which meters are operations is data: `operation_mapping` (`cost_operations` for g1t's bill, `billable_operations` for workspaces), read every 5 minutes, changed with `set_operation_mapping` without a deploy. Default: `git.fetch`, `git.receive_pack`, `internal.git.fetch`, `internal.git.receive_pack`, `binding.create`, `binding.fork`, `binding.delete` = 1, everything else 0. `git_operations` (what billing reads) is filled from the meters × `billable_operations`, by the hour. RPCs: `artifacts_usage { from, to, workspace?, by_repo? }` (raw meters and the mapping, for the reconciler), `operation_mapping`, `set_operation_mapping { meter, cost_operations, billable_operations, note? }`. Script: `scripts/ops/artifacts-usage.mjs`. |
358| R13 | Built | Nothing on the request path writes D1 for counting. Meters add up per isolate and are written in one batch from `ctx.wait_until` after every request (and at the end of the cron and queue handlers); a failed write is kept for the next. The free-workspace slow-down decides from counts the isolate read back after its last write plus what it added since (`git_ops::standing`, at most 10 minutes old) and billing's plan answer kept 5 minutes. The 63–98 ms `kept` step's D1 upsert is gone. A workspace whose counts this isolate never read is not slowed: nothing slows anyone on a guess. |
Pull request forks are removed a day after they merge or close, not a week359| R2 | Built; the fork storage test is yours to run | `pull.merged` and `pull.closed` set the fork's `retire_after` (`FORK_RETENTION_DAYS`, 1 day in production; 7 when unset); `pull.reopened` clears it, or makes the fork again. The hourly sweep (`23 * * * *`, 25 a run) keeps the fork's head in its repository as `refs/pull/<pull id>/head` (only missing objects travel; an empty pack when merged), records `retired_at` and `retired_head`, then deletes the fork from the store (a failed delete puts the row back). Reads of a retired fork (the pull request's changes, divergence, tree, blob, log, branches) are answered from the repository with the fork's branch mapped to the kept head (`forks.rs` `Viewed`). Anything that writes or uses git on it (git over HTTPS, `git_access`, catch-up, land, `delete_branch`) makes it again first (`revive`: fork, then move its branch to the head) and schedules it to go again. Work never emits `pull.reopened` today; the handler is ready for it. Script: `scripts/ops/fork-storage-test.mjs`. |
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily360| R3 | Built | Credentials g1t uses itself: TTL 3,600 s, reused for 50 minutes (isolate and KV, key `cred2:<key>:<scope>:internal`). `git_access` hands out its own: TTL 300 s, reused 180 s (`…:handout`); `refs_open` still uses 300 s. Every internal path (land, catch-up, mirrors, branch listing, commits, deleting a branch) now reuses kept credentials instead of minting each time. The remote is worked out as `https://<account>.artifacts.cloudflare.net/git/<namespace>/<name>.git` (the documented format, `api/git-protocol`), learned per namespace from the first `info()` an isolate makes, which runs alongside `createToken` and so costs no time; after that a mint is `get` and `createToken`. Optional `ARTIFACTS_REMOTE_BASE` skips even the first `info()`. |
361| R4 | Built | Pushes are read as they arrive (`request.stream()`) and walked by `pack_limits::PackSizer` (each object inflated into a 32 KiB window and thrown away): an object over 32 MB (a delta measured by the object it makes), or a push taking the repository and its forks (`stored_bytes`) past `REPO_STORAGE_LIMIT_BYTES` (950 MB), is declined with `ng` lines and `remote:` text; a repository already at the limit is refused at the push's `info/refs` in plain text. Up to 24 MiB is kept, scanned and sent on as one copy, not three. Past 24 MiB push protection cannot read the push, so it is declined (`LARGE_PUSHES=refuse`, failing closed) with a command to push in parts, the 100 MB network limit named; `LARGE_PUSHES=unscanned` streams it to the store instead, still size-checked (a violation ends the stream before the pack's checksum, so the store keeps nothing). A pack too large for the scanner to inflate (48 MB inflated) is declined the same way instead of let through. Landing streams: upload-pack's side-band answer is taken apart chunk by chunk (`pack_limits::Sideband`) straight into the receive-pack body. |
362| R5 | Built | `resilience.rs` sorts errors into rate limited, transient (`INTERNAL_ERROR`, `UPSTREAM_UNAVAILABLE`, `*_IN_PROGRESS`, no code, HTTP 5xx) and permanent. Binding reads, `get`, `info`, `createToken`, `create` and `delete` try up to 3 times with exponential backoff and jitter (80 ms base, 400 ms for rate limits, 2 s cap); `fork` and every receive-pack never retry. Git reads (`info/refs`, upload-pack) retry on 429 and 5xx. Per isolate, each namespace has a breaker that opens after 5 transient failures in a row, for 10 s, then lets one probe through. Busy answers reach git as 429 (rate limited) or 503, with `Retry-After: 5`; the site's read RPCs (`tree`, `blob`, `log`, `branches`, `blame`, `compare`) answer an `Outcome` failure saying the git storage is busy; other RPCs answer 503 with the same words. Health is counted by the minute (`store_health`) and served by the `store_health { minutes }` RPC; status.g1t.sh lists **Git storage** through a new `REPOS` service binding (down: 25% or more of at least 5 calls failed, or the breaker refused calls; degraded: rate limited, or a mean call over 1.5 s). |
363| R9 | Built | `log(branch)`, `branches()` and `read_file(ref, path)` are kept in the colo cache under the repository's `refs_version` (5 minutes at most, and only while `refs_cache::usable`), and by commit hash for good; a log by branch also fills the by-hash entry; `readCommit` (parents) is kept for good. Read RPCs open repositories through `read_git`, which sets the version. |
364| R10 | Built in repos; work unchanged | `divergence` works out the target's side once per target head per isolate (`coalesce.rs`: the head under the refs version, then the history by hash, kept 60 s), and what the target changed between two trees once per pair (10 minutes). `readCommit` and logs by hash come from the cache. Work's fan-out (`after_push`, up to 100 pull requests) is unchanged: its 100 `divergence` calls now cost one walk of the target instead of 100. |
365| R7 | Groundwork | `shards.rs`: bindings named in `ARTIFACTS_NAMESPACES` (JSON, binding → namespace; `ARTIFACTS` → `g1t` always there), a repository's namespace kept in its `store` column as `<namespace>/<key>` (no prefix means the `ARTIFACTS` namespace, so every existing key reads the same), new repositories placed by `ARTIFACTS_NEW_REPOS` (comma-separated, spread by an FNV hash of the repository id; names not bound are skipped), forks always in their repository's namespace, `ARTIFACTS_EU_NAMESPACE` reserved for EU residency (no workspace setting yet). Works with only `ARTIFACTS` bound, as today. |
366| R8 | Not touched | Sandbox clone depth belongs to the runner work (`crates/runner`). |
367
368### R1: reading `scripts/ops/artifacts-usage.mjs`
369
370```sh
371export CLOUDFLARE_API_TOKEN=<token with Account Analytics: Read (and D1: Read, or set CLOUDFLARE_D1_TOKEN)>
372node scripts/ops/artifacts-usage.mjs # last 31 days, a table
373node scripts/ops/artifacts-usage.mjs --days 7 --json > usage.json
374ARTIFACTS_NAMESPACE=g1t node scripts/ops/artifacts-usage.mjs
375```
376
377It prints, per day, Cloudflare's `pull`, `push`, `create`, `fork` and `delete` events and its
378errors beside g1t's fetch and push meters and `git_operations`, then, for each Cloudflare event,
379the ratio Cloudflare ÷ g1t for several combinations of meters. Read it like this:
380
381- `pull` ≈ `git.fetch + internal.git.fetch` (ratio 1.00): Cloudflare counts one pull per
382 upload-pack fetch, as assumed. Keep the default mapping.
383- `pull` ≈ a combination with `git.ls_refs` or `git.info_refs`: listing refs counts too. Set
384 `cost_operations` for those meters to 1 (`set_operation_mapping`), and decide whether
385 `billable_operations` follows (cost pass-through says yes).
386- Every ratio well under 1: Cloudflare counts per clone or fetch session, not per request.
387 Ratios over 1: something reaches Artifacts that g1t does not meter, such as sandboxes pushing
388 directly with handed-out credentials.
389- Only days after the meters were deployed compare; before that only `git_operations` exists.
g1t-runner 0.1.0 is released: signed binaries for five platforms at g1t.sh/downloads/runner; the Artifacts checks' results390- Binding calls do appear: Cloudflare's events include `read` and `token_create` actions (and
391 `namespace_*`) besides the five documented ones. If Cloudflare says they are billed, map the
392 `binding.*` meters in `operation_mapping`.
393
394A global API key works in place of the token: `CLOUDFLARE_API_KEY` with `CLOUDFLARE_EMAIL`
395(both scripts).
396
397**Result, 2026-10-06** (31 days; g1t's meters cover only 2026-10-06, the day they shipped):
398
399| Cloudflare event | 31 days | 2026-10-06 | g1t's meters, 2026-10-06 |
400| --- | --- | --- | --- |
401| `read` | 137,225 | 107,616 | `binding.get` 85,206, `read_file` 49,846, `read_tree` 9,208, `read_blob` 4,964, `log` 2,798 |
402| `pull` | 646 | 101 | `git.fetch` 29, `git.ls_refs` 26, `git.info_refs` 426 (+ 129 internal) |
403| `push` | 385 | 10 | `git.receive_pack` 3 |
404| `token_create` | 2,721 | 338 | `binding.create_token` 34 |
405| `fork` / `create` / `delete` | 96 / 14 / 8 | 2 / 0 / 0 | |
406| errors | 699 (688 client) | 476 client | |
407
408- `pull` is not one per upload-pack fetch: 101 pulls against 29 fetches on the one day both
409 exist. Over 31 days `pull` ≈ fetch + ls-refs + info/refs (ratio 1.06), so listing refs likely
410 counts as a pull. Not yet changed in `operation_mapping`: one day of meters is too little, and
411 re-check after a week before setting `cost_operations` for `git.info_refs` and `git.ls_refs`.
412- `read` is the open question that matters. If reads are billed as operations at $0.15 per
413 1,000, today's demo-scale traffic alone is about 3.2 million a month (~$480). Ask Cloudflare
414 (Q1) before 2026-10-14. Either way the volume is mostly waste: every repos call opens a handle
415 with `get` even when the answer is cached, and the object cache may not be hitting (no hit/miss
Fewer Artifacts reads: the store is asked for a handle only when needed, objects are kept in the isolate, and issue events read no workflows nobody listens for416 meter yet). Done on 2026-10-06: the handle's `get` waits for the first call that needs the
417 store (an answer from a cache, or `branches` over git, costs none); objects named by hash are
418 kept in the isolate (16 MB, oldest out first) ahead of the Cache API, and every look is
419 metered (`cache.memory_hit`, `cache.edge_hit`, `cache.miss`); issue and comment events start
420 nothing and read nothing when the synced `workflows` table has no workflow listening. Next:
421 read `cache.edge_hit` against `cache.miss` after a day; if the Cache API never hits from a
422 Worker reached only by service bindings, put objects in KV instead. Still to do: caller
423 attribution in the meters.
g1t-runner 0.1.0 is released: signed binaries for five platforms at g1t.sh/downloads/runner; the Artifacts checks' results424- 476 client errors on 2026-10-06 are unexplained; the fetch fix below accounts for some (every
425 failed negotiation was one).
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily426
427### R2: running and reading `scripts/ops/fork-storage-test.mjs`
428
429```sh
430export CLOUDFLARE_API_TOKEN=<token: Artifacts edit, Account Analytics read>
431node scripts/ops/fork-storage-test.mjs schema # which Artifacts analytics datasets exist
432node scripts/ops/fork-storage-test.mjs run --keep # namespace g1t-storage-test: 100 MB repository, 5 forks
433node scripts/ops/fork-storage-test.mjs measure # again tomorrow (storage is billed as a daily peak)
434node scripts/ops/fork-storage-test.mjs cleanup # delete the 6 repositories
435```
436
437It works only in its own namespace, through Cloudflare's REST API, never through g1t. Read:
438
439- Fork timing and response: a fork that returns in well under a second, with `objects` near the
440 source's count, is metadata (sharing). Seconds per fork, growing with size, suggests copying.
441- Storage figures (any dataset `schema` lists besides `artifactsEventsAdaptiveGroups`): about
442 100 MB after the forks means sharing; about 600 MB means each fork copied. The documentation
443 lists no storage dataset today, so this may print nothing: then the next day's usage in the
444 dashboard (Billing → Artifacts storage) is the measure, and the question stays with Cloudflare (Q2).
445- `events`: `storageLimitReached` or other errors during the test.
446
447Either way R2 retires forks; the answer decides `FORK_RETENTION_DAYS` (shared: a week is fine;
448copied: shorten it to 1 or 2 days and ask Cloudflare to raise the 1 TB account limit).
449
g1t-runner 0.1.0 is released: signed binaries for five platforms at g1t.sh/downloads/runner; the Artifacts checks' results450**Result, 2026-10-06: forks are stored and billed as copies.** A 100 MB source took 57 s to
451push; each of 5 forks took 4–6 s. The storage dataset (`artifactsStorageAdaptiveGroups`,
452`max.repositorySizeBytes`) gave every fork the source's full 105,582,592 bytes: about 633 MB for
453the six, not about 106 MB. Whatever Artifacts shares underneath, storage billing and the 1 TB
454account limit see full copies. So:
455
Pull request forks are removed a day after they merge or close, not a week456- `FORK_RETENTION_DAYS` is 1 in production (changed 2026-10-07).
g1t-runner 0.1.0 is released: signed binaries for five platforms at g1t.sh/downloads/runner; the Artifacts checks' results457- g1t meters a workspace's storage once per repository (`stored_bytes`), so an open pull
458 request's working copy is Cloudflare cost g1t absorbs: about $0.05 a month per 100 MB per open
459 pull request. Small now; decide whether open working copies count toward a workspace's
460 storage before agent pull requests reach thousands.
461- Ask Cloudflare whether forks share objects physically, and for a higher account limit.
462
463Also found while testing (fixed in `git_http.rs`): the store answers a protocol v2 fetch that is
464still negotiating, and whose `have`s it does not know, with `acknowledgments`, `NAK`, then a pack.
465git refuses that ("expected no other sections to be sent after no 'ready'"). g1t now ends such
466an answer after the acknowledgments with a flush, and the client negotiates again. Report it to
467Cloudflare.
468
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily469### R7: making more namespaces (yours to run, when needed)
470
471```sh
472# A US shard, unrestricted like today's g1t, and an EU one.
473curl -X POST "https://api.cloudflare.com/client/v4/accounts/1e6f2cffa3f445920836e8ebe446bb58/artifacts/namespaces" \
474 -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" \
475 --data '{"namespace":"g1t-us-1"}'
476curl -X POST "https://api.cloudflare.com/client/v4/accounts/1e6f2cffa3f445920836e8ebe446bb58/artifacts/namespaces" \
477 -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" \
478 --data '{"namespace":"g1t-eu","jurisdiction":"eu"}'
479```
480
481Then in `services/repos/wrangler.jsonc`:
482
483```jsonc
484"artifacts": [
485 { "binding": "ARTIFACTS", "namespace": "g1t" },
486 { "binding": "ARTIFACTS_1", "namespace": "g1t-us-1" },
487 { "binding": "ARTIFACTS_EU", "namespace": "g1t-eu" }
488],
489"vars": {
490 "ARTIFACTS_NAMESPACES": "{\"ARTIFACTS\":\"g1t\",\"ARTIFACTS_1\":\"g1t-us-1\",\"ARTIFACTS_EU\":\"g1t-eu\"}",
491 "ARTIFACTS_NEW_REPOS": "g1t,g1t-us-1", // new repositories spread over both
492 "ARTIFACTS_EU_NAMESPACE": "g1t-eu" // used once a workspace can choose the EU
493}
494```
495
496Existing repositories stay where they are (`store` without a prefix). Deploy the binding before
497naming its namespace in `ARTIFACTS_NEW_REPOS`; a name that is not bound is skipped, never used.
498Moving an existing repository between namespaces is not built (a clone and push, then a `store`
499update).
500
501### Deploy order and what to watch
502
5031. Migration 0011 (the deploy tool applies migrations first). `forks_of` reads `retired_at`, so
504 the new code must not run before it.
5052. `g1t-repos` (new vars in `wrangler.jsonc`; no new bindings).
5063. `g1t-status` (a new `REPOS` service binding; **Git storage** appears once deployed).
5074. After a day: `scripts/ops/artifacts-usage.mjs`; then the fork test.
508
509Expected: `Server-Timing` `kept` on POSTs drops from 63–98 ms to the KV and Cache lookups only (a
510few ms); `mint` happens about once per repository and scope every 50 minutes per isolate instead
511of every 3 minutes, and costs `get` and `createToken` (about 0.5 s instead of 0.8 s) once an
512isolate knows its namespace's prefix; branch pages and repeated tree and file views skip
513Artifacts while the refs version holds; a burst of 100 mergeability checks walks the target once.
514
515Risks: what Cloudflare bills is still theirs to confirm (the mapping makes changing it cheap).
516Counts held by an isolate that is evicted before its `wait_until` write are lost (seconds of
517traffic). Pushes over 24 MiB now fail closed: bringing a large existing repository needs the
518push-in-parts command (in `guides/git.md`). Moving a revived fork's branch back to its old head
519is a non-fast-forward update, which depends on Artifacts accepting it as git does by default; if
520it refuses, the fork of a closed pull request cannot be made again and the error says so.
521`refs/pull/*` refs appear in full ref advertisements (mirrors, `--mirror` clones). Operations on
522pull request forks are metered under the workspace `pulls`, so they are counted for g1t's bill
523but not charged to a workspace (except the fork itself, metered on its repository).