G5: the shelter — repos, grants and version history
The plane Windy Cloud does not have. Verified 2026-08-11: routes/storage.py and
its models contain ZERO occurrences of share/permission/acl/collaborat/seat/
version/snapshot/history/revision. This fills a hole rather than bolting onto
something that already had one.
- repos: create/list/get, repo_type required (I-7), reserved slugs, Gitea
reached ONLY through the membrane client (I-1)
- grants: human identity OR agent passport, exactly one enforced by a database
CHECK constraint; agent grants expire in 90 days by default
- versions: history in words a person recognises — no 'commit', no 'branch',
no 'repository' in any user-facing string (D-9/I-9), with a test that greps
the speak strings and fails on developer vocabulary
- private repos 404 rather than 403, so a stranger cannot learn one exists
Auth: three first-class caller classes (human OIDC / agent EPT / internal
service token), NO fourth, and no bypass env var — copied deliberately from the
desktop control server, the ecosystem's best Principle-#5 artifact.
G3.6 status-code law implemented: 400 and 404 REFUSE, 429/5xx retry then REFUSE.
A sibling maps 400/429 to 'unreachable' and soft-ALLOWS, which is inducible —
an attacker who wants the check skipped only has to make it rate-limit itself.
A test asserts resolve_passport has exactly one return path.
And I-8 applied to ourselves: G3.2's JWKS verifier does not exist yet, so the
human token path REFUSES in production rather than accepting an unverified JWT.
An unverified JWT is an authentication bypass, not a shortcut.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from api.app.buildinfo import get_build_info
|
||||
from api.app.config import get_settings
|
||||
@@ -24,7 +24,7 @@ from api.app.providers.registry import (
|
||||
GiteaProvider,
|
||||
R2Provider,
|
||||
)
|
||||
from api.app.routes import health
|
||||
from api.app.routes import health, repos
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -80,6 +80,9 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
app.state.settings = settings
|
||||
app.state.engine = engine
|
||||
app.state.sessionmaker = (
|
||||
async_sessionmaker(engine, expire_on_commit=False) if engine is not None else None
|
||||
)
|
||||
app.state.providers = [
|
||||
DatabaseProvider(engine),
|
||||
GiteaProvider(settings),
|
||||
@@ -112,6 +115,7 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
app.include_router(health.router)
|
||||
app.include_router(repos.router)
|
||||
|
||||
|
||||
@app.exception_handler(RepairPointer)
|
||||
|
||||
Reference in New Issue
Block a user