I-4 said 'never a one-way door' and had no implementation. Now it does.
- ensure the GitHub counterpart exists (idempotent), then ask Gitea to keep
it in step with sync_on_commit=True. An hourly timer means an hour of work
can be the thing you lose, and that window is invisible until it costs you.
- mirror status reports what is TRUE including 'we do not know'. An
unconfigured mirror reports unconfigured, NEVER healthy — same posture as
me-fleet.ts refusing to say 'online' when it only knows 'registered'.
- lag past the threshold is a P2, not a shrug. A mirror nobody checks is a
belief, not a backup, and this ecosystem already lost 37 days to a canary
everyone assumed was fine.
Gitea owns the replication rather than a hand-rolled loop, because a background
job that fails silently is exactly how the registry's integrity refresh spent
its entire life calling a 404 and incrementing a counter instead of raising.
Also fixes a real bug I had written myself: list_versions derived the Gitea
namespace from the CALLER, which is correct only while the caller is the owner
and addresses the wrong namespace the moment a collaborator asks — surfacing as
'not found', which is the hardest kind of bug to see. Now derived from the repo,
with a test that keeps it that way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
5.4 KiB
Python
136 lines
5.4 KiB
Python
"""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"
|
|
|
|
# Internal callers (the Cloud portal calling /internal/*). A first-class
|
|
# caller class, not a bypass: unset means service calls are REFUSED.
|
|
service_token: str = ""
|
|
|
|
# ⚠️ FAIL-CLOSED GATE. Full RS256/ES256 JWKS verification lands in G3.2.
|
|
# Until it does, the human token path must not be reachable in production —
|
|
# accepting an unverified JWT is not a shortcut, it is an authentication
|
|
# bypass. Agents are unaffected: their authority comes from a live Eternitas
|
|
# trust lookup, not from anything the token asserts about itself.
|
|
require_verified_jwt: bool = True
|
|
|
|
# ---- 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: I-4, never a one-way door --------------------------------
|
|
github_token: str = ""
|
|
github_owner: str = "sneakyfree"
|
|
# Gitea's timer, as a backstop. sync_on_commit is what actually matters:
|
|
# an hourly window means an hour of work can be the thing you lose.
|
|
mirror_interval: str = "8h0m0s"
|
|
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()
|