An audit found 102 FK columns with no index — every join, per-parent
listing, and the delete cascades from _delete_cascade_policy scanned
whole tables on them (deployments.app_id, audit_logs.user_id,
dns_records.zone_id, webhook_logs.webhook_id, ...). All FK columns now
declare index=True; migration 096 creates the same indexes on existing
installs, deriving the list from the live schema at upgrade time (so
it is idempotent and cannot drift from what an install actually has).
Ratchet: test_every_fk_column_is_indexed pins the rule with no
allowlist — a new FK column is born indexed or the build fails.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opt-in to_dict over __table__.columns: auto-isoformat datetimes,
*_encrypted always dropped, __serialize_exclude__ for redacted columns,
serialize_extra() for computed fields (named to dodge ServerMetrics'
literal 'extra' column). 20 purely-mechanical to_dicts across 15 files
convert; genuinely custom shapes keep their hand-written methods (plan
75 §F's ruling stands). Field selection stays api/_query.py's job.
Gate: key-for-key parity against the pre-conversion key sets frozen
from git HEAD (only the Timestamp pair may be additive).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A variable can now land on one compose service instead of all of them. Both
EnvironmentVariable and SharedVariable gain an optional target_service
(NULL = all services), threaded through the shared resolver as an inclusion
filter (a var applies to a service when it targets all OR that service).
EnvService.get_effective_env_for_services(app, services) computes each service's
merged env (all-services vars + that-service vars, local overriding shared), and
ComposeEnvService builds the override's per-service environment from it — so a
var targeted at "db" only appears under the db service, untargeted vars appear
everywhere. The single-container docker-run path is unchanged (one container
gets the full effective env regardless of target).
API: env-var create/update and shared-variable create/update accept
target_service; GET /apps/<id>/compose-services lists a compose app's services.
UI: the Environment tab shows an "Applies to" selector (gated to compose apps via
the service list) with a per-row target chip; the shared variable editor gains a
target-service field.
Migration 036 adds the two columns; single Alembic head; chain applies from
empty. Proving tests cover per-service distribution + the resolver helper. Full
backend suite: 949 passed, 14 skipped; frontend builds clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Project / Environment hierarchy (C2): Workspace -> Project -> Environment
org model. New Project/Environment models + CRUD APIs, nullable
applications.project_id/environment_id (opt-in, no destructive backfill),
ProjectService.ensure_default, a Projects list + ProjectDetail (env tabs),
a project/environment selector in the create wizard, and sidebar nav.
- Polymorphic shared resources facade (C9): ResourceTag + SharedVariableGroup
/SharedVariable/attachment models keyed by (resource_type, resource_id),
reusing the env-var Fernet encryption; SharedResourceService resolves merged
variables (most-recent attachment wins) with secrets masked. /api/v1/shared
endpoints + reusable TagsPanel/EnvironmentVariablesPanel + a Shared Variables
management page. Existing per-resource tables untouched (facade first).
- PR preview environments (C3): ApplicationPreview + settings models, a
PreviewService with pure domain-templating + reconcile (open PRs vs active
previews) cores and best-effort provisioning (incl. guarded WordPress
file+DB clone), a pull_request webhook endpoint, preview.* jobs, and a
Previews tab on the app detail page.
- Per-server managed proxy stack (C6): opt-in Dockerized Traefik/Caddy as a
Compose stack (host Nginx stays default and superior for PHP/WordPress).
ProxyStack model + ProxyStackService with pure compose/config generators and
versioned config backups, /api/v1/servers/<id>/proxy endpoints, and a Proxy
tab on the server detail page.
Migrations 031-034 add the new tables/columns; the full chain applies cleanly
from an empty DB. Full backend suite: 871 passed, 14 skipped; frontend builds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>