Remove bare plan-section citations

Comments cited plan sections as "(§G3)" or "(§8 unification)", which
point into the same git-ignored plans the plan-number citations did.
References next to a real document (docs/REDESIGN_MAP.md §3) stay. Code
and runtime strings are unchanged (AST and token comparison).
This commit is contained in:
Juan Denis committed 2026-10-10 20:25:49 -04:00
1 parent e94537d0a4
commit ddaeb549ad
34 files changed
+53 -53

No files matched your search

+1 -1
View File
@@ -281,7 +281,7 @@ def create_app(config_name=None):
from app.services.dns_zone_service import DNSZoneService
n_zones = DNSZoneService.link_legacy_zones()
# Fold a legacy single-row email relay config into the unified
# EmailProviderConnection table (§6); idempotent, best-effort.
# EmailProviderConnection table; idempotent, best-effort.
try:
from app.services.email_relay_service import EmailRelayService
EmailRelayService.migrate_legacy_config()
+1 -1
View File
@@ -1,6 +1,6 @@
# Bucket: PER-APP read. Per-app history gates via _access →
# can_access_app. See docs/WORKSPACE_SCOPING.md.
"""Unified deployment surface (§3 unification).
"""Unified deployment surface.
One read namespace over the deploy records that today live in three places:
versioned build ``Deployment`` rows, webhook-triggered ``GitDeployment`` rows,
+1 -1
View File
@@ -33,7 +33,7 @@ def _get_snapshot_or_404(app_id, snap_id):
# "Config Checkpoint" is the user-facing name for a deployment config snapshot
# (§8 decision: rename in the UI; keep /snapshots working). Every route below is
# (decision: rename in the UI; keep /snapshots working). Every route below is
# also reachable under /config-checkpoints so the API matches the new wording.
@snapshots_bp.route('/<int:app_id>/snapshots', methods=['GET'])
@snapshots_bp.route('/<int:app_id>/config-checkpoints', methods=['GET'])
+1 -1
View File
@@ -39,7 +39,7 @@ class AppSlot(db.Model):
# What the app listens on inside the container; the same for both slots.
container_port = db.Column(db.Integer, nullable=True)
container_name = db.Column(db.String(128), nullable=True)
# Compose slots (§C): the slot's own compose project.
# Compose slots: the slot's own compose project.
project_name = db.Column(db.String(128), nullable=True)
# Immutable image this slot runs (serverkit-app-<id>:d<deployment> or a digest).
image_ref = db.Column(db.String(255), nullable=True)
+1 -1
View File
@@ -5,7 +5,7 @@ from app import db
from app.models.mixins import TimestampMixin
from app.models.json_column_mixin import JsonColumnMixin
# Every CORE data-protection target the unified policy system can back up (§8).
# Every CORE data-protection target the unified policy system can back up.
# 'application' is the original target; 'database', 'files', and 'server' were
# folded in from the legacy BackupService so a single BackupPolicy/BackupRun
# system covers all data-protection backups.
+1 -1
View File
@@ -34,7 +34,7 @@ class DeploymentJob(RunLifecycleMixin, JsonColumnMixin, db.Model):
requested_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True, index=True)
trigger = db.Column(db.String(30), default='manual')
# Links to the release ledgers (§3 unification): the DeploymentJob is the
# Links to the release ledgers: the DeploymentJob is the
# canonical execution record; Deployment / GitDeployment remain the
# version/release ledgers it produces. All nullable — a template-install job
# has none of these set.
+1 -1
View File
@@ -30,7 +30,7 @@ class EmailProviderConnection(TimestampMixin, JsonColumnMixin, db.Model):
is_default = db.Column(db.Boolean, default=False, index=True)
is_active = db.Column(db.Boolean, default=True)
# Usage flags (§6 unification): one connection can power the Notification Bus
# Usage flags: one connection can power the Notification Bus
# and/or the Postfix outbound relay. API providers (SendGrid/Postmark/SES/
# Mailgun) can't be a Postfix smarthost, so only 'smtp' rows set uses_relay.
uses_notifications = db.Column(db.Boolean, default=True, nullable=False)
+3 -3
View File
@@ -77,7 +77,7 @@ class EmailProviderService:
def default_provider(cls):
"""The provider the Notification Bus should send through: the active
default, else the first active one — both restricted to providers
flagged ``uses_notifications`` so relay-only rows are never picked (§6)."""
flagged ``uses_notifications`` so relay-only rows are never picked."""
row = EmailProviderConnection.query.filter_by(
is_default=True, is_active=True, uses_notifications=True).first()
if row:
@@ -120,7 +120,7 @@ class EmailProviderService:
if make_default:
EmailProviderConnection.query.update({EmailProviderConnection.is_default: False})
# Usage flags (§6). API providers cannot be a Postfix smarthost, so
# Usage flags. API providers cannot be a Postfix smarthost, so
# uses_relay is only honored for SMTP.
uses_relay = bool(data.get('uses_relay')) and provider == 'smtp'
row = EmailProviderConnection(
@@ -142,7 +142,7 @@ class EmailProviderService:
@classmethod
def update_usage(cls, provider_id, fields):
"""Toggle a provider's usage flags (§6): uses_notifications, uses_relay,
"""Toggle a provider's usage flags: uses_notifications, uses_relay,
relay_priority. uses_relay is only honored for SMTP connections."""
row = cls.get_provider(provider_id)
if not row:
@@ -1,8 +1,8 @@
"""Name an app's likely bottleneck from what the panel measures.
A rule-based doctor, no AI: it reads the app's request metrics (§A2), its
A rule-based doctor, no AI: it reads the app's request metrics, its
container's CPU, and, for a PostgreSQL / MySQL it reaches through
``fromService``, that database's CPU and top queries (§A3). Each hint names
``fromService``, that database's CPU and top queries. Each hint names
the component that fits the signal, links to where it is set up, and states
that component's failure mode. Nothing is ever added automatically.
+1 -1
View File
@@ -121,7 +121,7 @@ class CronService:
def _read_crontab(cls) -> Optional[str]:
"""The current user's crontab, or ``None`` if it could not be read.
``None`` is "could not read", never "empty crontab" (§A). An empty
``None`` is "could not read", never "empty crontab". An empty
crontab is ``''`` and ``crontab -l`` exits non-zero for it on some
implementations, so callers that install a replacement treat a failed
read as "start from nothing" only where that was already the behaviour.
+1 -1
View File
@@ -46,7 +46,7 @@ class DbAdminSsoService:
# ── choke-point: ALL docker interaction goes through here (tests stub it) ──
@classmethod
def _docker(cls, args, timeout=60):
"""Run ``docker <args>`` — via DockerService.run (§G3).
"""Run ``docker <args>`` — via DockerService.run.
The named seam stays because this service's tests stub it; the wrapper
body does not.
@@ -150,7 +150,7 @@ class DbConfigTunerService:
@classmethod
def _docker(cls, args, timeout=60):
"""Run a docker CLI command — via DockerService.run (§G3).
"""Run a docker CLI command — via DockerService.run.
The named seam stays (tests stub it). The private copy collapsed a
missing docker and a timed-out docker into the same ``str(e)``, so a
@@ -161,7 +161,7 @@ class DbConfigTunerService:
@classmethod
def _exec_sql(cls, target, sql):
"""Run SQL against the target's engine — via ``db_exec`` (§G5).
"""Run SQL against the target's engine — via ``db_exec``.
``target``: {'container', 'engine', 'user', 'password'}.
Returns {'success', 'output', 'error'} with machine-readable output
+1 -1
View File
@@ -29,7 +29,7 @@ from typing import Optional
from app.services.database_service import DatabaseService
# The names the rest of the codebase spells these engines with. Kept here so an
# unknown engine is *reported*, never silently routed to mysql (§A).
# unknown engine is *reported*, never silently routed to mysql.
MYSQL_ALIASES = ('mysql', 'mariadb')
POSTGRES_ALIASES = ('postgresql', 'postgres', 'pgsql')
+5 -5
View File
@@ -7,28 +7,28 @@ uses, so a default changes in one place.
import json
DEFAULTS = {
# Health gate (§A3): how long a new release may take to answer, whether a
# Health gate: how long a new release may take to answer, whether a
# 4xx on the health path still counts as up, and how many answers in a row
# it takes.
'healthcheck_timeout': 120,
'healthcheck_allow_4xx': False,
'healthcheck_consecutive': 3,
# How many deployments' images stay on disk for rollback (§A1).
# How many deployments' images stay on disk for rollback.
'keep_images': 3,
# Slot deploys (§B): how long to watch a release after the switch, how
# Slot deploys: how long to watch a release after the switch, how
# long the old slot stays warm, and the operator's word that two copies
# sharing the app's volumes for a moment is safe.
'watch_seconds': 60,
'standby_warm_minutes': 10,
'slot_volumes_confirmed': False,
# Release phase (§D): a command to run once before the switch (overrides
# Release phase: a command to run once before the switch (overrides
# the Procfile's release: line), whether to dump the app's databases
# first, and the announced-downtime fallback for schema changes that
# cannot stay backward-compatible for one deploy.
'release_command': None,
'snapshot_databases': True,
'stop_old_before_release': False,
# Compose slots (§C), written by the panel, never by the settings API:
# Compose slots, written by the panel, never by the settings API:
# the web service, the port it published before slots, and whether the
# stateful services have moved to the shared data project.
'compose_web': None,
+3 -3
View File
@@ -142,7 +142,7 @@ class DeploymentService:
if not pull_result.get('success'):
return cls._fail(app, deployment, pull_result.get('error') or 'Git pull failed')
# create_deployment read HEAD before this pull, so the row named
# the commit that was live, not the one being deployed (§A2).
# the commit that was live, not the one being deployed.
cls._record_commit(app, deployment)
if cls._uses_compose(app):
@@ -168,7 +168,7 @@ class DeploymentService:
deploy_stages.mark('Build')
# An immutable tag per deployment: a rollback then redeploys
# the image that actually ran, not whatever :latest became (§A1).
# the image that actually ran, not whatever :latest became.
from app.services.app_image_retention import deploy_image_tag
build_result = BuildService.build(
app_id,
@@ -727,7 +727,7 @@ class DeploymentService:
# A compose app's code lives in its images: checking out the old
# commit changed files and nothing else, so the "rollback"
# reported success while the new release kept serving (§A6).
# reported success while the new release kept serving.
# Rebuild and recreate from the checked-out tree, validated first.
if cls._uses_compose(app):
from app.services import deploy_preflight_service as preflight
+1 -1
View File
@@ -1040,7 +1040,7 @@ class DockerService:
@classmethod
def run_compose(cls, cmd, *, cwd=None, timeout=None):
"""Run an already-built compose argv; the shared result shape (§G1).
"""Run an already-built compose argv; the shared result shape.
Separate from :meth:`run` because the argv head is whatever
``_get_compose_cmd()`` resolved — ``docker compose`` (v2) or
+1 -1
View File
@@ -1,7 +1,7 @@
"""Outbound SMTP relay (smarthost) for the mail server.
The relay is now one row of the unified ``EmailProviderConnection`` table — an
``smtp`` connection flagged ``uses_relay`` (§6 unification) — so a single store
``smtp`` connection flagged ``uses_relay`` — so a single store
drives both the Notification Bus and the Postfix relay. The legacy single-row
``email_relay_config`` is migrated in on first boot and kept only as a read
fallback during the deprecation window.
+1 -1
View File
@@ -135,7 +135,7 @@ class FirewallService:
# unprivileged LXC, where ufw exists and is on PATH but fails on
# dropped CAP_NET_ADMIN) or an exception is "could not check": None,
# never a fabricated inactive — the shape a past outage warned about,
# verified live in the §D capability-restricted container.
# verified live in a capability-restricted container.
active = None if installed else False
error = None
if installed:
+1 -1
View File
@@ -101,7 +101,7 @@ class GitService:
def _git_out(app_path, *args, timeout=None):
"""stdout of a read-only ``git -C <app_path> <args>``, or ``None``.
``None`` means the command did not succeed — not "empty output" (§A).
``None`` means the command did not succeed — not "empty output".
``get_commit_info`` alone had five copies of
``subprocess.run(...); x = result.stdout.strip() if result.returncode
== 0 else None``, which is this function written out by hand each time.
@@ -7,7 +7,7 @@ Phase-4 matrix, and it is deliberately narrow:
- No free text ever reaches a shell. Every parameter is validated against a
typed schema (enum / bounded int) before the executor runs.
- Each template declares (id, label, param schema, executor, min_role). The
min_role reuses the same capability tiers as the rest of the plan
min_role reuses the panel's capability tiers
(can_operate_app for 'member', can_admin_app for 'admin').
- Every run is audit-logged.
- The whole surface is feature-flagged (config MEMBER_ACTIONS_ENABLED).
@@ -15,7 +15,7 @@ Phase-4 matrix, and it is deliberately narrow:
The registry ships one concrete template today — `set-backup-frequency`, which
adjusts an app's backup schedule through the existing BackupPolicyService (no new
side-channel). The cron "ping this app's scheduler every N minutes" template
named in the plan is deferred until its ScheduledJob wiring lands;
is deferred until its ScheduledJob wiring lands;
adding it is just another registry entry.
"""
from app.services.resource_grant_service import ResourceGrantService
+3 -3
View File
@@ -990,7 +990,7 @@ location /p/ {{
the cache file, so a fixed-string grep for ``KEY: http[s]://<host>/``
finds exactly this site's files; the trailing ``/`` keeps
``shop.example.com`` from matching ``shop.example.com.evil``. Entries
cached under the pre-§B3 key (before the vhost was rewritten) aren't
cached under an older key (before the vhost was rewritten) aren't
found this way, and expire on their own TTL.
Without ``hosts`` the whole shared directory is wiped (every site).
@@ -1118,7 +1118,7 @@ location /p/ {{
def read_vhost(cls, name: str) -> Optional[str]:
"""The vhost's current content, or ``None`` if it cannot be read.
``None`` means "could not determine", never "empty file" (§A). Callers
``None`` means "could not determine", never "empty file". Callers
that need to distinguish a missing vhost from an unreadable one check
``os.path.exists`` themselves.
"""
@@ -1143,7 +1143,7 @@ location /p/ {{
@classmethod
def write_vhost(cls, name: str, content: str, *, enable: bool = True) -> Dict:
"""Write, enable, config-test, roll back on failure, reload. One door (§G4).
"""Write, enable, config-test, roll back on failure, reload. One door.
``environment_domain_service`` re-implemented this whole sequence with
raw ``sudo`` calls, and ``nginx_advanced_service`` wrote the file with a
+1 -1
View File
@@ -272,7 +272,7 @@ class ProcessService:
@classmethod
def get_systemd_unit_status(cls, unit_name: str) -> Dict:
"""Check whether a systemd unit is active — via ``unit_is_active`` (§G6).
"""Check whether a systemd unit is active — via ``unit_is_active``.
The probe itself was re-inlined in four other services; this keeps the
dict shape its own callers read and lets one implementation answer.
+1 -1
View File
@@ -83,7 +83,7 @@ def sanitize(text: str) -> str:
# ---------------------------------------------------------------------------
# Failure hints (D6 / §5.3): first match wins, evaluated against the failure tail
# Failure hints: first match wins, evaluated against the failure tail
# at close time. One sentence, action-first, never jokey.
# ---------------------------------------------------------------------------
HINTS: List = [
+4 -4
View File
@@ -6,7 +6,7 @@ fails. This is the same idea for the apps ServerKit runs:
1. the new release boots in the IDLE slot, on its own loopback port, while
the live slot keeps serving;
2. it must pass the health gate there (§A3) — a release that never becomes
2. it must pass the health gate there — a release that never becomes
healthy is torn down and the site never noticed;
3. the switch is ``app.port = <idle slot port>`` + ``write_app_vhost`` (which
runs ``nginx -t`` and restores the old vhost on failure) — so every reader
@@ -388,7 +388,7 @@ class SlotDeployService:
reuse = (idle.image_ref == image and idle.state in ('standby', 'stopped')
and idle.container_name and DockerService.get_container(idle.container_name))
# ---- snapshot + release (§D): once, before the switch, old slot live
# ---- snapshot + release: once, before the switch, old slot live
from app.services import release_phase_service as release
stage('Snapshot')
release.snapshot_databases(app, deployment, log)
@@ -500,7 +500,7 @@ class SlotDeployService:
return None
# ------------------------------------------------------------------ #
# Deploy (compose, §C)
# Deploy (compose)
# ------------------------------------------------------------------ #
@classmethod
def deploy_compose(cls, app, deployment, log=None, stage=None, reuse_standby=False) -> Dict:
@@ -643,7 +643,7 @@ class SlotDeployService:
return cls.deploy_compose(app, rollback_deployment, log=log)
# ------------------------------------------------------------------ #
# The one-time data split (§C)
# The one-time data split
# ------------------------------------------------------------------ #
@classmethod
def compose_split_preview(cls, app) -> Dict:
+3 -3
View File
@@ -660,8 +660,8 @@ class TemplateService:
# SERVICE_FQDN_<NAME> -> the app's auto-assigned hostname (best-effort)
# SERVICE_URL_<NAME> -> full URL derived from the FQDN (+ scheme)
# SERVICE_BASE64_<NAME> -> base64 of a freshly generated secret
# SERVERKIT_METRICS_TOKEN -> the panel's Prometheus scrape token (plan
# 86 §A4), so a monitoring template can scrape
# SERVERKIT_METRICS_TOKEN -> the panel's Prometheus scrape token, so a
# monitoring template can scrape
# /api/v1/fleet-monitor/prometheus
#
# Resolution is PURE and unit-testable: no Docker, no network. The only
@@ -2470,7 +2470,7 @@ class TemplateService:
if not compose_result.get('success'):
# Rollback — and say whether it worked: an unchecked `up` here
# reported a plain failure while the app sat stopped (§A6).
# reported a plain failure while the app sat stopped.
if os.path.exists(backup_path):
shutil.copy(backup_path, compose_path)
restored = DockerService.compose_up(app_path, detach=True)
+2 -2
View File
@@ -386,7 +386,7 @@ def write_privileged_file(path: str, content: str, *, append: bool = False,
``success`` is never True on a write that did not demonstrably happen — a
``FileNotFoundError`` (no ``tee``) or a timeout reports the failure instead
of being swallowed into a cheerful return (§A, probe honesty).
of being swallowed into a cheerful return (probe honesty).
*mode* and *owner* are applied after the write (``chmod`` / ``chown``); a
failure there is reported, because a config written with the wrong
@@ -437,7 +437,7 @@ _COMMAND_SEARCH_DIRS = ('/usr/local/sbin', '/usr/local/bin', '/usr/sbin',
def unit_is_active(unit: str, *, timeout: int = 10) -> Optional[bool]:
"""Tri-state systemd unit probe: ``True``, ``False``, or ``None`` (§G6).
"""Tri-state systemd unit probe: ``True``, ``False``, or ``None``.
``None`` means *could not determine* — no systemctl, no systemd, a probe
that timed out — and is deliberately not ``False``. The five hand-rolled
+1 -1
View File
@@ -39,7 +39,7 @@ import {
routesForPlacement,
} from './routes/routeManifest';
// /apps/* is the legacy URL space for what is now "Services" (§1 unification).
// /apps/* is the legacy URL space for what is now "Services".
// The list route already redirects; this preserves deep links to a single app
// (and its active tab) by forwarding to the matching /services/* path.
function LegacyAppRedirect() {
+1 -1
View File
@@ -18,7 +18,7 @@ import GunicornTab from '../components/service-detail/GunicornTab';
import CommandsTab from '../components/service-detail/CommandsTab';
import OverviewTab from '../components/service-detail/OverviewTab';
// Container Ops / WAF / Build / Deploy (merged in from the retired
// ApplicationDetail page in §1) now live under the Settings sub-nav, so they're
// ApplicationDetail page) now live under the Settings sub-nav, so they're
// imported by SettingsTab rather than rendered as top-level tabs here.
import PreviewList from '../components/previews/PreviewList';
import EmptyState from '../components/EmptyState';
+1 -1
View File
@@ -87,7 +87,7 @@ const Terminal = () => {
// ─── Interactive shell tab (demo console: target rail + terminal pane) ───
// Sessions run over the ServerKit agent (terminal:create); the panel host has
// no PTY endpoint (§5 gap), so the rail lists paired agent servers only.
// no PTY endpoint, so the rail lists paired agent servers only.
const TerminalShellTab = () => {
const { t } = useTranslation();
const [servers, setServers] = useState([]);
+1 -1
View File
@@ -11,7 +11,7 @@ export async function getApps(options = {}) {
}
// "Services" is the user-facing term for what the backend calls Applications
// (§1 unification). These aliases let new code speak "services" while the
//. These aliases let new code speak "services" while the
// API client and backend model keep the canonical `apps`/`Application` names.
export async function getAppServices(options = {}) {
return getApps.call(this, options);
@@ -58,7 +58,7 @@ export async function deleteBackupRun(targetType, targetId, runId) {
return this.request(`${runsBase(targetType, targetId)}/${runId}`, { method: 'DELETE' });
}
// ---- fleet-wide policy + run views (§8 unification) -----------------------
// ---- fleet-wide policy + run views -----------------------
// The per-target methods above answer "how is THIS site protected?"; these two
// answer "how is everything protected?" and back the Backups overview — the
// protected-resource count, the activity heatmap, and the success rate all come
+1 -1
View File
@@ -133,7 +133,7 @@ export async function installCertbot() {
return this.request('/ssl/install-certbot', { method: 'POST' });
}
// Advanced SSL endpoints — unified under the one /ssl surface (§5). The old
// Advanced SSL endpoints — unified under the one /ssl surface. The old
// /ssl/advanced/* paths still resolve (deprecated alias).
export async function getSSLProfiles() {
return this.request('/ssl/profiles');
+1 -1
View File
@@ -21,7 +21,7 @@ export async function restoreSnapshot(appId, snapId) {
}
// "Config Checkpoint" is the user-facing name for a deployment config snapshot
// (§8). These aliases let new UI code use the new term; the backend serves both
//. These aliases let new UI code use the new term; the backend serves both
// /snapshots and /config-checkpoints.
export async function getConfigCheckpoints(appId, limit = 50) {
return getAppSnapshots.call(this, appId, limit);
+1 -1
View File
@@ -4,7 +4,7 @@
// cyan #49c7f0 / violet #b07bf5, neutral gray #646b7a.
const SERVICE_TYPES = {
// Tab order note: ops/waf/build/deploy were merged in from the retired
// ApplicationDetail page (§1 feature unification), then relocated into the
// ApplicationDetail page, then relocated into the
// Settings sub-nav to declutter the top strip — Container Ops + Git & Deploy
// + Build live under Settings, WAF under Settings → Security. Type-gating of
// those sections is handled in SettingsTab (ops = Docker only; waf = Docker +