G2/G4 complete: Gitea live, LFS landing in R2, four traps pinned

VERIFIED END TO END from outside the network:
  - create repo via API -> clone -> commit -> push -> read back over HTTPS
  - 3 MB LFS object pushed through the tunnel, landed in R2 at lfs/34/2d/...
  - NO local lfs/ directory on the host: I-3 confirmed by measurement
  - /health/full: db, gitea and r2 all green

Adds strand G4A recording four traps that each cost a crash loop, with tests:
  1. [lfs] STORAGE_TYPE creates a separate storage section that does not
     inherit [storage] — crash loop, error names the symptom not the cause
  2. storage backend != LFS enabled; LFS_START_SERVER is separate, and its
     absence reads as a permissions error
  3. Gitea env-to-ini SETS but never UNSETS — removing a compose var leaves the
     line in the persisted app.ini, so repo config and prod config silently
     disagree. Exactly the drift this cell exists to end.
  4. R2 rejects the default S3 checksum algorithm

And G4A.5, which is architecture rather than a bug: an 8 MB non-LFS push died
with HTTP 524 at Cloudflare's ~100s limit. This makes the LFS threshold
load-bearing and REQUIRES G10 to serve model weights via presigned R2 URLs
rather than proxying blobs through the tunnel — client straight to R2, which is
what Hugging Face does and which takes Grant's home upstream out of the path.

G2.4: Gitea's MIT text and a NOTICE now travel with the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-11 15:13:58 -04:00
parent 647d414ec1
commit 82044933ed
4 changed files with 142 additions and 0 deletions

View File

@@ -265,6 +265,54 @@ Strands G0–G4 are sequential. G5–G12 are concurrent once G4 lands.
- **G4.6** Quota: on push, call the kernel's quota check; over-quota → refuse with a repair-pointer error whose `speak` is grandma-words and whose `remediation_tool` names the upgrade path (Storage-Kingdom cross-sell hook, §0.5). **We emit the hook; the kernel owns the price** (I-11). - **G4.6** Quota: on push, call the kernel's quota check; over-quota → refuse with a repair-pointer error whose `speak` is grandma-words and whose `remediation_tool` names the upgrade path (Storage-Kingdom cross-sell hook, §0.5). **We emit the hook; the kernel owns the price** (I-11).
- **G4.7** Object-count and byte accounting per repo, recomputed nightly, exposed on `GET /{id}/status`. - **G4.7** Object-count and byte accounting per repo, recomputed nightly, exposed on `GET /{id}/status`.
## Strand G4A — Gitea/R2 traps paid for on 2026-08-11
Four failures hit while wiring G2–G4 live. Each cost a crash loop or a dead end,
and each presents as a different problem than it is. All four are now pinned by
tests in `api/tests/test_invariants.py`.
- **G4A.1 — `[lfs] STORAGE_TYPE` breaks storage inheritance.** Naming a storage
type inside `[lfs]` creates a **separate** storage section that does *not*
inherit the endpoint or credentials from `[storage]`. Gitea crash-loops on
`Endpoint: does not follow ip address or domain name standards` — an error that
names the symptom and not the cause. **Let LFS inherit `[storage]`.** Avatars
initialising correctly is the tell that the rest of the config is fine.
- **G4A.2 — setting the storage backend does not turn LFS on.** `[server]
LFS_START_SERVER = true` is separate. Without it the batch endpoint 404s and
the client reports *"Repository or object not found"*, which reads like a
permissions or credentials problem and is neither.
- **G4A.3 — ⚠️ Gitea's env-to-ini SETS but never UNSETS.** Removing a `GITEA__*`
variable from compose does **not** remove the line from the persisted
`app.ini`. The container will keep booting with a setting that no longer exists
anywhere in the repo, so the config in git and the config in production
silently disagree — the exact class of drift this whole cell exists to end.
**To remove a setting you must edit `app.ini` on the host**
(`/srv/windygit/git/gitea/conf/app.ini`), not just the compose file.
- **G4A.4 — R2 rejects the default S3 checksum algorithm.** Set
`MINIO_CHECKSUM_ALGORITHM = md5` or uploads fail with an opaque checksum error.
### G4A.5 — ⚠️ Cloudflare's 100-second limit caps a push, and it shapes G10
A plain (non-LFS) push of an 8 MB file over the tunnel died with **HTTP 524**.
Cloudflare's proxy times out at ~100 s on the Free plan, and Grant's residential
upstream measured ~19 KB/s during the LFS test, so anything large is a coin flip.
This is not a bug to fix; it is a constraint that **dictates the model-hub
architecture**:
1. **The LFS threshold (G4.5) is load-bearing, not tidiness.** Anything big must
go via LFS, because a large blob inside a git pack has no way to be resumed
or offloaded.
2. **G10 must serve LFS objects via presigned R2 URLs — client straight to R2 —
rather than proxying blobs through the tunnel.** That removes the 100 s
ceiling, removes Grant's home upstream from the path entirely, and is exactly
what Hugging Face does. Until that lands, model repos are capped by whatever
fits in 100 seconds.
**Verified working on 2026-08-11:** a 3 MB LFS object pushed through the tunnel
and landed in R2 at `lfs/34/2d/…`, with **no local `lfs/` directory on the host**
— I-3 confirmed by measurement rather than by assertion.
## Strand G5 — THE SHELTER (permissions plane over Windy Cloud) · **the v0 product** ## Strand G5 — THE SHELTER (permissions plane over Windy Cloud) · **the v0 product**
*This strand is the one that ships first and the one that has no competitor. Windy Cloud today has no sharing, no permissions and no versioning of any kind (D-8).* *This strand is the one that ships first and the one that has no competitor. Windy Cloud today has no sharing, no permissions and no versioning of any kind (D-8).*

24
LICENSES/NOTICE.md Normal file
View File

@@ -0,0 +1,24 @@
# Third-party notices — Windy Git
Windy Git runs **stock Gitea** as an unforked component (D-2 / I-1). Gitea is
distributed under the MIT license, reproduced in `gitea-MIT.txt`.
MIT's only obligation is that the copyright notice and license text travel with
copies of the software that are **distributed**. Running Windy Git as a hosted
service is not distribution, so strictly this file is not required today — it is
here anyway, because it will be required the moment a self-host bundle ships, and
because shipping it costs nothing.
MIT does **not** require us to advertise the lineage, does not restrict
commercial use, and does not require publishing our modifications. That last
point is why Gitea (MIT) was chosen over Forgejo (GPLv3 from v9): GPLv3 does not
trigger on running a service — that is AGPL, and it is widely gotten wrong — but
it **does** trigger on distributing a binary, which the self-host line would do.
**Gitea's trademarks are not used.** The product is branded Windy Git throughout.
| Component | Version | License |
|---|---|---|
| Gitea | 1.24.6 (pinned) | MIT — `gitea-MIT.txt` |
| PostgreSQL | 16 | PostgreSQL License |
| cloudflared | 2026.1.2 | Apache-2.0 |

20
LICENSES/gitea-MIT.txt Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2016 The Gitea Authors
Copyright (c) 2015 The Gogs Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -234,3 +234,53 @@ def test_g05_no_secret_literals_committed():
text = path.read_text(encoding="utf-8", errors="ignore") text = path.read_text(encoding="utf-8", errors="ignore")
for pattern in patterns: for pattern in patterns:
assert not pattern.search(text), f"credential literal in {path}" assert not pattern.search(text), f"credential literal in {path}"
# --------------------------------------------------------------------------
# G2.1 / G2.7 — the Gitea version is PINNED, and drift is a failure
# --------------------------------------------------------------------------
def test_g21_gitea_version_is_pinned_not_latest():
compose = (ROOT / "docker-compose.yml").read_text()
m = re.search(r"image:\s*\S*gitea/gitea:(\S+)", compose)
assert m, "no gitea image pin found"
assert m.group(1) != "latest", "G2.1: pin an exact Gitea version, never `latest`"
assert re.match(r"^\d+\.\d+\.\d+$", m.group(1)), f"not an exact version: {m.group(1)}"
def test_g24_gitea_license_travels_with_us():
"""MIT's one obligation. Cheap to honour, embarrassing to miss."""
assert (ROOT / "LICENSES" / "gitea-MIT.txt").exists()
assert "MIT" in (ROOT / "LICENSES" / "gitea-MIT.txt").read_text()
# --------------------------------------------------------------------------
# G4.3 — the two Gitea storage traps that cost a crash loop each
# --------------------------------------------------------------------------
def test_g43_no_lfs_storage_type_override():
"""Naming a storage type inside [lfs] creates a SEPARATE storage section
that does not inherit endpoint or credentials from [storage], and Gitea
crash-loops with an error that names the symptom and not the cause."""
# Check real settings only — the compose file deliberately NAMES this key in
# a warning comment so the next person does not re-add it.
active = [
ln for ln in (ROOT / "docker-compose.yml").read_text().splitlines()
if ln.strip() and not ln.strip().startswith("#")
]
assert not any("GITEA__lfs__STORAGE_TYPE" in ln for ln in active)
def test_g43_lfs_server_is_actually_enabled():
"""Setting the storage backend does NOT turn LFS on. Without this the batch
endpoint 404s and the client says 'Repository or object not found', which
reads like a permissions problem and is not one."""
active = [
ln for ln in (ROOT / "docker-compose.yml").read_text().splitlines()
if ln.strip() and not ln.strip().startswith("#")
]
assert any("GITEA__server__LFS_START_SERVER" in ln for ln in active)
def test_g43_r2_checksum_trap_is_pinned():
"""R2 rejects the checksum algorithm S3 clients send by default."""
compose = (ROOT / "docker-compose.yml").read_text()
assert "MINIO_CHECKSUM_ALGORITHM" in compose