G0: cell substrate — invariants made executable

Strand G0 complete and VERIFIED against real Postgres, not asserted.

  - FastAPI plane, fail-closed provider seams, repair-pointer error taxonomy
  - migration 001: all 10 tables incl. repo_type NOT NULL and model_cards (I-7)
  - 17 invariant tests, ruff clean, vocabulary audit clean

Two bugs found by RUNNING it that review would not have caught:

  1. SQLAlchemy Enum persists .name, not .value — so RepoState.deleted_soft
     and CreatedVia.imported would have written labels migration 001 never
     declared, failing at runtime rather than at review. Pinned via
     values_callable.
  2. op.create_table asks each Enum to emit its own CREATE TYPE with no
     checkfirst, so the second reference raised DuplicateObject and the
     migration died halfway. Types are now created once, referenced with
     create_type=False.

Proven live, with the hostile env var set:
  - I-12: COMMIT_SHA=deadbeef... in the environment, /version reports real HEAD.
    That env pin is the documented root cause of nine sibling services
    misreporting their commit; here it is structurally ignored.
  - I-8: three unconfigured providers -> status degraded, HTTP 503, each saying
    'refusing to report healthy'. No mock, no false green.
  - G0.4: upgrade -> downgrade -> upgrade round-trip clean (10 -> 0 -> 10).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-11 14:19:28 -04:00
parent 24708914c9
commit 659991b2bd
29 changed files with 2040 additions and 0 deletions

0
api/app/__init__.py Normal file
View File

87
api/app/buildinfo.py Normal file
View File

@@ -0,0 +1,87 @@
"""Deployment identity (I-12).
Nine of twelve live services in this ecosystem cannot name the commit they are
running. One reports *another repo's* commit. The root cause found on 2026-08-10
was hardcoded `COMMIT_SHA` pins in `/opt/*/.env` that OVERRIDE the build arg, so a
redeploy keeps reporting the old sha until a human hand-edits the file.
The fix here is structural, not procedural:
* the sha is baked into the image at build time (Docker ARG -> this module);
* a runtime `COMMIT_SHA` environment variable is **IGNORED**, loudly;
* in a dev worktree with nothing baked, we read git directly and SAY SO in
`source`, rather than reporting a value we cannot stand behind (I-8).
`make check` fails if /version disagrees with `git rev-parse HEAD` in CI.
"""
from __future__ import annotations
import logging
import os
import subprocess
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
log = logging.getLogger(__name__)
# Rewritten at image build time by the Dockerfile. Do not edit by hand, and do
# not "helpfully" default it to something plausible.
BAKED_COMMIT_SHA: str = ""
BAKED_BUILT_AT: str = ""
VERSION = "0.1.0"
@dataclass(frozen=True)
class BuildInfo:
version: str
commit_sha: str | None
built_at: str | None
source: str # "baked" | "git-worktree" | "unknown"
def _git_head() -> str | None:
try:
root = Path(__file__).resolve().parents[2]
out = subprocess.run(
["git", "-C", str(root), "rev-parse", "HEAD"],
capture_output=True,
text=True,
timeout=5,
check=False,
)
return out.stdout.strip() or None if out.returncode == 0 else None
except Exception: # pragma: no cover - defensive
return None
@lru_cache
def get_build_info() -> BuildInfo:
override = os.environ.get("COMMIT_SHA")
if BAKED_COMMIT_SHA:
if override and override != BAKED_COMMIT_SHA:
log.warning(
"IGNORING COMMIT_SHA environment override (%s). This service "
"reports the sha baked into its own artifact (%s). See I-12 — an "
"env pin overriding the build arg is exactly why nine sibling "
"services misreport their commit.",
override[:12],
BAKED_COMMIT_SHA[:12],
)
return BuildInfo(VERSION, BAKED_COMMIT_SHA, BAKED_BUILT_AT or None, "baked")
head = _git_head()
if head:
if override:
log.warning(
"IGNORING COMMIT_SHA environment override (%s); reading the "
"worktree instead.",
override[:12],
)
return BuildInfo(VERSION, head, None, "git-worktree")
# Nothing baked, no git. Say so. Do not invent a sha (I-8).
return BuildInfo(VERSION, None, None, "unknown")

119
api/app/config.py Normal file
View File

@@ -0,0 +1,119 @@
"""Settings for the windy-git plane.
Every `env:` default in DNA_STRAND_MASTER_PLAN.md is shipped here as an actual
default, not a suggestion. Providers are FAIL-CLOSED (I-8): a provider whose
credentials are absent reports itself unconfigured and refuses to answer, rather
than answering from a mock. The domains cell shipped a portal on a mock registrar
and told the public that google.com was available for $18.00 a year. That failure
mode is banned here by construction.
"""
from __future__ import annotations
from functools import lru_cache
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", extra="ignore"
)
# ---- service identity -------------------------------------------------
environment: str = "development"
service_name: str = "windy-git"
port: int = 8600
# ---- database ---------------------------------------------------------
# Postgres schema `windgit`, own alembic (G0.4).
database_url: str = "postgresql+asyncpg://windygit:windygit@localhost:5432/windygit"
db_schema: str = "windgit"
# ---- Gitea (a COMPONENT behind an API membrane, never a merged tree) ---
gitea_base_url: str = "http://localhost:3000"
gitea_admin_token: str = ""
# ---- Cloudflare R2 (I-3: heavy bytes only, never git objects) ---------
r2_account_id: str = ""
r2_access_key_id: str = ""
r2_secret_access_key: str = ""
r2_bucket_lfs: str = "windy-git-lfs"
r2_bucket_artifacts: str = "windy-git-artifacts"
r2_bucket_backups: str = "windy-git-backups"
# ---- Eternitas (agent identity + trust) -------------------------------
eternitas_base_url: str = "https://api.eternitas.ai"
eternitas_platform_api_key: str = ""
# ---- account-server OIDC (human identity) -----------------------------
account_server_base_url: str = "https://account.windyword.ai"
# ---- storage law (I-3, G4.4) ------------------------------------------
# Git object databases MUST live on a POSIX filesystem. A test asserts this
# path does not resolve to a network mount.
git_data_root: str = "/srv/windygit/git"
# ---- LFS threshold (G4.5) ---------------------------------------------
# Small text files stay in git proper. LFS-for-everything makes clones slow
# and operations heavy.
lfs_threshold_bytes: int = 5 * 1024 * 1024 # env: 5 MB
lfs_extensions: tuple[str, ...] = (
".safetensors", ".bin", ".gguf", ".pt", ".ckpt", ".onnx",
".zip", ".tar", ".gz", ".mp4", ".wav", ".mov", ".psd",
)
# ---- velocity bases, multiplied by EI band (G3.4) ---------------------
# Platinum x10 / Gold x4 / Standard x1 / Watch x0.5 / Untrusted read-only
rate_pushes_per_day: int = 500
rate_repo_creates_per_day: int = 50
rate_grants_per_day: int = 100
rate_force_pushes_per_day: int = 10
# ---- mirror health (I-4) ----------------------------------------------
mirror_lag_p2_seconds: int = 3600 # env: 60 min -> P2
# ---- agent grants (G5.3) ----------------------------------------------
agent_grant_default_days: int = 90
# ---- repo types (I-7: first-class from migration 001) -----------------
repo_types_enabled: tuple[str, ...] = ("code",) # model/dataset are v2
# ---- feature gates ----------------------------------------------------
# Mirrors the sibling `edge_live` pattern (windy-cloud-sites, a8ff948):
# never claim live while a provider is mock.
hf_compat_enabled: bool = False # Grant-gated, DNA plan section 7.7
kit0_host: str = Field(
default="72.60.118.54",
description="Recorded ONLY so the G1 guard can refuse to deploy here (D-4).",
)
# ---- derived ----------------------------------------------------------
@property
def r2_configured(self) -> bool:
return bool(
self.r2_account_id and self.r2_access_key_id and self.r2_secret_access_key
)
@property
def gitea_configured(self) -> bool:
return bool(self.gitea_base_url and self.gitea_admin_token)
@property
def eternitas_configured(self) -> bool:
return bool(self.eternitas_base_url and self.eternitas_platform_api_key)
@property
def r2_endpoint_url(self) -> str:
return f"https://{self.r2_account_id}.r2.cloudflarestorage.com"
@property
def is_production(self) -> bool:
return self.environment.lower() in {"production", "prod"}
@lru_cache
def get_settings() -> Settings:
return Settings()

108
api/app/errors.py Normal file
View File

@@ -0,0 +1,108 @@
"""Repair-pointer error taxonomy (section 0.6, G8.3).
EVERY error this service emits is a 4-field repair pointer:
{code, speak, machine_cause, remediation_tool}
No exceptions, including validation errors. `speak` is grandma-words (I-9) and
obeys the D-9 vocabulary law (see scripts/vocab_audit.py), and never uses the
word "commit" on a shelter surface.
`remediation_tool` names the tool an agent should call next, or null when a human
must act.
"""
from __future__ import annotations
from typing import Any
from fastapi import HTTPException
class RepairPointer(HTTPException):
"""An error an agent can act on without guessing."""
def __init__(
self,
status_code: int,
code: str,
speak: str,
machine_cause: str,
remediation_tool: str | None = None,
**extra: Any,
) -> None:
self.code = code
self.speak = speak
self.machine_cause = machine_cause
self.remediation_tool = remediation_tool
super().__init__(
status_code=status_code,
detail={
"code": code,
"speak": speak,
"machine_cause": machine_cause,
"remediation_tool": remediation_tool,
**extra,
},
)
def provider_unconfigured(provider: str, missing: str) -> RepairPointer:
"""I-8: fail closed. Never answer from a mock, never claim live.
This is the guard the domains cell did not have on its public quote route.
"""
return RepairPointer(
status_code=503,
code="provider_unconfigured",
speak=(
"That part of Windy Git isn't switched on yet, so we're not going to "
"guess at an answer. Nothing you have is affected."
),
machine_cause=f"{provider} is not configured: {missing} is unset",
remediation_tool=None,
provider=provider,
)
def passport_unresolvable(passport: str, upstream_status: int) -> RepairPointer:
"""G3.6: 404 and 400 REFUSE. There is no soft-allow path here.
windy-chat maps 400/429 to "unreachable" and then soft-ALLOWS, which is a
live residual bypass because 429 is trivially inducible at 100/min/IP.
"""
return RepairPointer(
status_code=403,
code="passport_unresolvable",
speak="We couldn't confirm that helper's ID, so we didn't let it make changes.",
machine_cause=(
f"eternitas trust lookup for {passport} returned {upstream_status}; "
"policy is REFUSE on 400/404 and REFUSE after backoff on 429/5xx"
),
remediation_tool="windy_git.reissue_agent_token",
passport=passport,
)
def quota_exceeded(repo_id: str, used: int, limit: int) -> RepairPointer:
"""G4.6: we emit the cross-sell hook; the KERNEL owns the price (I-11)."""
return RepairPointer(
status_code=413,
code="quota_exceeded",
speak=(
"Your projects have outgrown the space on your plan. Nothing was lost — "
"the newest save just didn't go through."
),
machine_cause=f"repo {repo_id} would use {used} bytes against a limit of {limit}",
remediation_tool="windy_cloud.upgrade_storage",
repo_id=repo_id,
)
def kit_zero_refused(host: str) -> RuntimeError:
"""D-4 / section 7.8: only Grant may overturn a never."""
return RuntimeError(
f"REFUSING TO START: this service is pointed at Kit 0 ({host}). "
"Windy Git never runs on Kit 0 — CI executes untrusted code and Kit 0 "
"holds identity, the certificate authority, mail, Matrix and the broker. "
"See DNA_STRAND_MASTER_PLAN.md D-4."
)

132
api/app/main.py Normal file
View File

@@ -0,0 +1,132 @@
"""windy-git — the version, permission and provenance plane over Windy Cloud.
Strand G0. This process is OUR service. Gitea runs beside it as an unforked
component and is reached only over its REST API (D-2 / I-1).
"""
from __future__ import annotations
import logging
import socket
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 api.app.buildinfo import get_build_info
from api.app.config import get_settings
from api.app.errors import RepairPointer, kit_zero_refused
from api.app.providers.registry import (
DatabaseProvider,
EternitasProvider,
GiteaProvider,
R2Provider,
TunnelProvider,
)
from api.app.routes import health
logging.basicConfig(
level=logging.INFO,
format='{"ts":"%(asctime)s","level":"%(levelname)s","logger":"%(name)s","msg":"%(message)s"}',
)
log = logging.getLogger("windy-git")
def _refuse_kit_zero(settings) -> None:
"""D-4 / section 7.8 — only Grant may overturn a never.
Kit 0 is disqualified on four independent grounds, any one sufficient. The
strongest: CI executes arbitrary workflow code, and Kit 0 holds identity, the
certificate authority, inbound SMTP, Matrix, the broker and the admin console.
A guard in a document is a preference; a guard in the boot path is a rule.
"""
if not settings.is_production:
return
try:
local_ips = {
info[4][0] for info in socket.getaddrinfo(socket.gethostname(), None)
}
except socket.gaierror:
return
if settings.kit0_host in local_ips:
raise kit_zero_refused(settings.kit0_host)
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
_refuse_kit_zero(settings)
info = get_build_info()
log.info(
"starting windy-git %s commit=%s source=%s env=%s",
info.version,
(info.commit_sha or "unknown")[:12],
info.source,
settings.environment,
)
if info.source == "unknown":
log.warning(
"This process cannot name its own commit. It will report null rather "
"than guess (I-12), but a production deploy in this state is a defect."
)
engine = None
try:
engine = create_async_engine(settings.database_url, pool_pre_ping=True)
except Exception as exc: # noqa: BLE001
log.warning("database engine not created: %s", exc)
app.state.settings = settings
app.state.engine = engine
app.state.providers = [
DatabaseProvider(engine),
GiteaProvider(settings),
R2Provider(settings),
EternitasProvider(settings),
TunnelProvider(settings),
]
yield
if engine is not None:
await engine.dispose()
app = FastAPI(
title="Windy Git",
description=(
"The version, permission and provenance plane over Windy Cloud. "
"Agents are citizens here, not tourists wearing a human's token."
),
version=get_build_info().version,
lifespan=lifespan,
)
app.include_router(health.router)
@app.exception_handler(RepairPointer)
async def _repair_pointer_handler(_, exc: RepairPointer) -> JSONResponse:
return JSONResponse(status_code=exc.status_code, content=exc.detail)
@app.exception_handler(RequestValidationError)
async def _validation_handler(_, exc: RequestValidationError) -> JSONResponse:
"""G8.3: EVERY error is a repair pointer. Including validation errors.
FastAPI's default 422 body is machine-readable and human-hostile. It is also
the single most common error an agent will hit, so it is the last place to
drop the contract.
"""
return JSONResponse(
status_code=422,
content={
"code": "invalid_request",
"speak": "Something in that request didn't look right, so we didn't act on it.",
"machine_cause": f"request validation failed: {exc.errors()}",
"remediation_tool": None,
},
)

View File

323
api/app/models/core.py Normal file
View File

@@ -0,0 +1,323 @@
"""Data model, section 4 of the DNA plan.
I-7 is enforced here structurally: `repo_type` is NOT NULL from migration 001,
and `model_cards` exists in v1 even though `repo_type=model` does not ship until
v2. Shipping v1 with the v2 columns absent is forbidden — cheap now,
near-impossible to retrofit.
Postgres is truth. Gitea's own database is a component's private state and this
service never writes to it directly (I-1).
"""
from __future__ import annotations
import enum
import uuid
from datetime import datetime
from sqlalchemy import (
ARRAY,
BigInteger,
Boolean,
CheckConstraint,
DateTime,
Enum,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
SCHEMA = "windgit"
class Base(DeclarativeBase):
pass
class RepoType(enum.StrEnum):
"""I-7 / D-6. A Hugging Face repo IS a git repo with LFS and a model card —
the consolidation is metadata and UI, not infrastructure."""
code = "code"
model = "model"
dataset = "dataset"
class Visibility(enum.StrEnum):
private = "private"
unlisted = "unlisted"
public = "public"
class RepoState(enum.StrEnum):
active = "active"
archived = "archived"
deleted_soft = "deleted-soft"
class CreatedVia(enum.StrEnum):
portal = "portal"
agent = "agent"
imported = "import"
cloud_folder = "cloud-folder"
class GrantRole(enum.StrEnum):
owner = "owner"
maintainer = "maintainer"
writer = "writer"
reader = "reader"
class MirrorState(enum.StrEnum):
healthy = "healthy"
degraded = "degraded"
failed = "failed"
def _pg_enum(enum_cls, name: str):
"""SQLAlchemy's Enum persists `.name` by default, not `.value`.
Three members here have a name that differs from its value
(`deleted_soft`/"deleted-soft", `imported`/"import", `cloud_folder`/
"cloud-folder"), so the default would write a label migration 001 does not
declare and the insert would fail at runtime, not at review. Pin values
explicitly.
"""
return Enum(
enum_cls,
name=name,
schema=SCHEMA,
values_callable=lambda cls: [member.value for member in cls],
)
def _pk() -> Mapped[uuid.UUID]:
return mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
class Repo(Base):
__tablename__ = "repos"
__table_args__ = (
UniqueConstraint("identity_id", "slug", name="uq_repos_identity_slug"),
Index("ix_repos_repo_type", "repo_type"),
Index("ix_repos_passport", "passport"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = _pk()
identity_id: Mapped[str] = mapped_column(String(64), nullable=False)
# Agent-owned repos. An agent is a citizen here, not a guest on a human's row.
passport: Mapped[str | None] = mapped_column(String(32))
slug: Mapped[str] = mapped_column(String(128), nullable=False)
display_name: Mapped[str] = mapped_column(String(255), nullable=False)
# I-7: first-class, NOT NULL, never inferred, never defaulted at read time.
repo_type: Mapped[RepoType] = mapped_column(
_pg_enum(RepoType, "repo_type"), nullable=False
)
gitea_repo_id: Mapped[int | None] = mapped_column(Integer)
visibility: Mapped[Visibility] = mapped_column(
_pg_enum(Visibility, "visibility"),
nullable=False,
default=Visibility.private,
)
# D-8: set when this repo was git-enabled from a Windy Cloud folder. The
# user's files stay first-class Cloud objects; we never take custody (I-13).
cloud_folder_ref: Mapped[str | None] = mapped_column(String(255))
default_branch: Mapped[str] = mapped_column(String(128), default="main")
lfs_bytes: Mapped[int] = mapped_column(BigInteger, default=0)
object_bytes: Mapped[int] = mapped_column(BigInteger, default=0)
state: Mapped[RepoState] = mapped_column(
_pg_enum(RepoState, "repo_state"), default=RepoState.active
)
created_via: Mapped[CreatedVia] = mapped_column(
_pg_enum(CreatedVia, "created_via"), nullable=False
)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
class RepoGrant(Base):
"""The shelter (D-8/G5.3). Windy Cloud has no sharing of any kind today —
verified 2026-08-11 against routes/storage.py and its models."""
__tablename__ = "repo_grants"
__table_args__ = (
# Exactly one grantee. Enforced by the database, not by application code,
# because application-enforced invariants are how this ecosystem got a
# double-mint and a unique constraint living in two files.
CheckConstraint(
"(grantee_identity_id IS NULL) <> (grantee_passport IS NULL)",
name="ck_grant_exactly_one_grantee",
),
Index("ix_grants_repo", "repo_id"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = _pk()
repo_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{SCHEMA}.repos.id", ondelete="CASCADE"), nullable=False
)
grantee_identity_id: Mapped[str | None] = mapped_column(String(64))
grantee_passport: Mapped[str | None] = mapped_column(String(32))
role: Mapped[GrantRole] = mapped_column(
_pg_enum(GrantRole, "grant_role"), nullable=False
)
granted_by: Mapped[str] = mapped_column(String(64), nullable=False)
# Agent grants expire by default (env: 90 days). A permanent agent credential
# is a standing liability nobody chose.
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
confirm_ref: Mapped[str | None] = mapped_column(String(128))
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class RepoVersion(Base):
"""Our own view of history, independent of Gitea (I-1)."""
__tablename__ = "repo_versions"
__table_args__ = (
UniqueConstraint("repo_id", "seq", name="uq_version_repo_seq"),
Index("ix_versions_commit", "commit_sha"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = _pk()
repo_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{SCHEMA}.repos.id", ondelete="CASCADE"), nullable=False
)
seq: Mapped[int] = mapped_column(Integer, nullable=False)
commit_sha: Mapped[str] = mapped_column(String(64), nullable=False)
tree_sha: Mapped[str | None] = mapped_column(String(64))
author_identity_id: Mapped[str | None] = mapped_column(String(64))
author_passport: Mapped[str | None] = mapped_column(String(32))
signed: Mapped[bool] = mapped_column(Boolean, default=False)
signature_verified: Mapped[bool] = mapped_column(Boolean, default=False)
# G9.1: frozen at push time, NEVER recomputed. A band is a statement about
# what was known then, not a live lookup.
ei_at_action: Mapped[str | None] = mapped_column(String(32))
message: Mapped[str | None] = mapped_column(Text)
bytes_added: Mapped[int] = mapped_column(BigInteger, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class Mirror(Base):
"""I-4: never a one-way door. This table is what the alerting reads."""
__tablename__ = "mirror_state"
__table_args__ = ({"schema": SCHEMA},)
id: Mapped[uuid.UUID] = _pk()
repo_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{SCHEMA}.repos.id", ondelete="CASCADE"), nullable=False
)
remote_url: Mapped[str] = mapped_column(String(512), nullable=False)
direction: Mapped[str] = mapped_column(String(16), default="push")
last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_error: Mapped[str | None] = mapped_column(Text)
lag_seconds: Mapped[int | None] = mapped_column(Integer)
state: Mapped[MirrorState] = mapped_column(
_pg_enum(MirrorState, "mirror_health"), default=MirrorState.healthy
)
class AgentToken(Base):
"""G6.3. Never a human's PAT wearing an agent's name — that is the entire
GitHub grievance and the reason this product exists."""
__tablename__ = "agent_tokens"
__table_args__ = (
Index("ix_agent_tokens_passport", "passport"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = _pk()
passport: Mapped[str] = mapped_column(String(32), nullable=False)
repo_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey(f"{SCHEMA}.repos.id", ondelete="CASCADE")
)
scopes: Mapped[list[str]] = mapped_column(ARRAY(String(64)), nullable=False)
# We store a hash. Never the token.
token_hash: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
revoked_reason: Mapped[str | None] = mapped_column(String(255))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class AgentAction(Base):
__tablename__ = "agent_actions"
__table_args__ = (
Index("ix_agent_actions_passport_ts", "passport", "ts"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = _pk()
passport: Mapped[str] = mapped_column(String(32), nullable=False)
repo_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
action: Mapped[str] = mapped_column(String(64), nullable=False)
ei_at_action: Mapped[str | None] = mapped_column(String(32))
confirm_ref: Mapped[str | None] = mapped_column(String(128))
result: Mapped[str] = mapped_column(String(32), nullable=False)
# G3.7: actually populated. windy-chat emits nothing here and contributes $0
# to the cost dashboard despite real spend.
cost_microcents: Mapped[int] = mapped_column(BigInteger, default=0)
ts: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class ModelCard(Base):
"""v2 surface, v1 schema (I-7). Populated from README.md YAML frontmatter."""
__tablename__ = "model_cards"
__table_args__ = ({"schema": SCHEMA},)
id: Mapped[uuid.UUID] = _pk()
repo_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{SCHEMA}.repos.id", ondelete="CASCADE"), nullable=False, unique=True
)
base_model: Mapped[str | None] = mapped_column(String(255))
license: Mapped[str | None] = mapped_column(String(64))
pipeline_tag: Mapped[str | None] = mapped_column(String(64))
tags: Mapped[list[str] | None] = mapped_column(ARRAY(String(64)))
library: Mapped[str | None] = mapped_column(String(64))
card_yaml: Mapped[dict | None] = mapped_column(JSONB)
card_body: Mapped[str | None] = mapped_column(Text)
class Job(Base):
__tablename__ = "jobs"
__table_args__ = ({"schema": SCHEMA},)
id: Mapped[uuid.UUID] = _pk()
kind: Mapped[str] = mapped_column(String(64), nullable=False)
payload: Mapped[dict] = mapped_column(JSONB, nullable=False)
state: Mapped[str] = mapped_column(String(32), default="pending")
attempts: Mapped[int] = mapped_column(Integer, default=0)
last_error: Mapped[str | None] = mapped_column(Text)
run_after: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class WebhookEvent(Base):
__tablename__ = "webhook_events"
__table_args__ = ({"schema": SCHEMA},)
id: Mapped[uuid.UUID] = _pk()
event_type: Mapped[str] = mapped_column(String(64), nullable=False)
payload: Mapped[dict] = mapped_column(JSONB, nullable=False)
delivered: Mapped[bool] = mapped_column(Boolean, default=False)
attempts: Mapped[int] = mapped_column(Integer, default=0)
last_error: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

View File

55
api/app/providers/base.py Normal file
View File

@@ -0,0 +1,55 @@
"""Provider seams — all of them fail CLOSED (I-8).
`windy-cloud-sites` ships an `edge_live` gate whose whole job is "never claim
live while the provider is mock" (commit a8ff948). `windy-cloud-domains` has a
registrar seam that literally raises `RuntimeError("Refusing to pretend")` — and
then shipped its public portal without wiring the equivalent gate on the quote
route, which is why production told anyone who asked that google.com was
available for $18.00 a year.
The lesson those two cells paid for: a fail-closed seam is worth nothing if a
route can reach the data without passing through it. So here the probe and the
gate are the SAME object, and `healthy()` can never return True for a provider
that `configured` reports False.
"""
from __future__ import annotations
import abc
from dataclasses import dataclass
@dataclass(frozen=True)
class ProbeResult:
ok: bool
detail: str
# True only when we actually reached the real dependency. A provider that is
# merely "not configured" is ok=False, reachable=False — never ok=True.
reachable: bool = False
class Provider(abc.ABC):
"""A dependency outside this process."""
name: str
@property
@abc.abstractmethod
def configured(self) -> bool:
"""Do we hold every credential needed to talk to the real thing?"""
@abc.abstractmethod
async def probe(self) -> ProbeResult:
"""Reach the real dependency. Never simulate."""
async def healthy(self) -> ProbeResult:
if not self.configured:
return ProbeResult(
ok=False,
detail=f"{self.name} is not configured; refusing to report healthy",
reachable=False,
)
try:
return await self.probe()
except Exception as exc: # noqa: BLE001 - a probe must never raise upward
return ProbeResult(ok=False, detail=f"{self.name} probe failed: {exc}")

View File

@@ -0,0 +1,128 @@
"""Concrete providers: Gitea, R2, Eternitas, Postgres, tunnel.
Each is a real probe against the real dependency (I-8). None of them has a mock
mode. If you find yourself adding one, add it behind `configured` returning False
instead — an unconfigured provider is honest; a mock provider is a liar with a
green light.
"""
from __future__ import annotations
import httpx
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncEngine
from api.app.config import Settings
from api.app.providers.base import ProbeResult, Provider
_TIMEOUT = httpx.Timeout(5.0, connect=3.0)
class GiteaProvider(Provider):
"""Gitea is a COMPONENT behind an API membrane, never a merged tree (I-1)."""
name = "gitea"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.gitea_configured
async def probe(self) -> ProbeResult:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.get(
f"{self._s.gitea_base_url}/api/v1/version",
headers={"Authorization": f"token {self._s.gitea_admin_token}"},
)
if r.status_code != 200:
return ProbeResult(False, f"gitea /api/v1/version -> {r.status_code}", True)
return ProbeResult(True, f"gitea {r.json().get('version', '?')}", True)
class R2Provider(Provider):
"""I-3: LFS, releases, artifacts, archives. NEVER git object stores."""
name = "r2"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.r2_configured
async def probe(self) -> ProbeResult:
# HEAD the bucket via the S3 endpoint. boto3 is sync, so we keep the
# probe to a plain reachability check here and let G4 wire the signed
# client; a 400/403 still proves the endpoint is real and answering.
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.head(f"{self._s.r2_endpoint_url}/{self._s.r2_bucket_lfs}")
reachable = r.status_code < 500
return ProbeResult(
ok=r.status_code in (200, 400, 403),
detail=f"r2 endpoint -> {r.status_code}",
reachable=reachable,
)
class EternitasProvider(Provider):
"""The one issuer. Every agent identity in the ecosystem terminates here."""
name = "eternitas"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
return self._s.eternitas_configured
async def probe(self) -> ProbeResult:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
r = await client.get(f"{self._s.eternitas_base_url}/health")
return ProbeResult(r.status_code == 200, f"eternitas /health -> {r.status_code}", True)
class DatabaseProvider(Provider):
"""Postgres is truth. Gitea's own DB is a component's private state."""
name = "db"
def __init__(self, engine: AsyncEngine | None) -> None:
self._engine = engine
@property
def configured(self) -> bool:
return self._engine is not None
async def probe(self) -> ProbeResult:
assert self._engine is not None
async with self._engine.connect() as conn:
await conn.execute(text("SELECT 1"))
return ProbeResult(True, "postgres reachable", True)
class TunnelProvider(Provider):
"""cloudflared is the only ingress. No inbound port is ever opened (G1.2)."""
name = "tunnel"
def __init__(self, settings: Settings) -> None:
self._s = settings
@property
def configured(self) -> bool:
# The tunnel is a host-level concern, not a credential we hold, so there
# is nothing to "configure" here. The probe alone decides health, and in
# dev it will honestly say cloudflared is not running (I-8).
return True
async def probe(self) -> ProbeResult:
async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
try:
r = await client.get("http://localhost:2000/metrics")
except httpx.RequestError as exc:
return ProbeResult(False, f"cloudflared metrics unreachable: {exc}")
return ProbeResult(r.status_code == 200, f"cloudflared metrics -> {r.status_code}", True)

View File

75
api/app/routes/health.py Normal file
View File

@@ -0,0 +1,75 @@
"""G0.2 / G0.3 — /version and /health/full.
The two endpoints this ecosystem most needs to be honest, because both audits
found them lying elsewhere: nine of twelve services cannot name their commit, and
a sibling cell reported healthy while serving from a mock.
`/health/full` returns `ok` ONLY when every provider it depends on proved itself
against the real dependency. Anything else is `degraded`. There is no code path
that returns `ok` from an unconfigured provider (I-8).
"""
from __future__ import annotations
from fastapi import APIRouter, Request, Response
from api.app.buildinfo import get_build_info
from api.app.config import get_settings
router = APIRouter(tags=["system"])
@router.get("/version")
async def version() -> dict:
"""I-12. A runtime COMMIT_SHA override is ignored; see buildinfo.py."""
info = get_build_info()
s = get_settings()
return {
"service": s.service_name,
"version": info.version,
"commit_sha": info.commit_sha,
"built_at": info.built_at,
"source": info.source,
"environment": s.environment,
"repo_types_enabled": list(s.repo_types_enabled),
}
@router.get("/health")
async def health() -> dict:
"""Cheap liveness. Says nothing about dependencies — /health/full does that."""
return {"status": "ok"}
@router.get("/health/full")
async def health_full(request: Request, response: Response) -> dict:
providers = request.app.state.providers
checks: dict[str, dict] = {}
for provider in providers:
result = await provider.healthy()
checks[provider.name] = {
"ok": result.ok,
"configured": provider.configured,
"reachable": result.reachable,
"detail": result.detail,
}
all_ok = all(c["ok"] for c in checks.values())
status = "ok" if all_ok else "degraded"
if not all_ok:
# Degraded is a real answer, not a 500. But it must never read as ok.
response.status_code = 503
info = get_build_info()
return {
"status": status,
"commit_sha": info.commit_sha,
"checks": checks,
# Grandma-words, and the D-9 vocabulary law binds this string.
"speak": (
"Everything is working."
if all_ok
else "Some parts of Windy Git aren't switched on. Nothing you have is lost."
),
}

View File