Functional Requirements¶
This section formalizes normative requirements and measurable outcomes. Requirements are numbered
FR-### (Functional Requirement) and outcomes SC-### (Success Criterion). This structure supports
test mapping, due diligence review, and implementation tracking. (Source: §40; FRs §40.1, outcomes §40.2.)
Each FR has a stable anchor (#fr-065) so historical citations and the SRE guide's Part-D
traceability resolve. Success criteria are on the Success Criteria page.
How to cite
Link a requirement as [FR-065](functional-requirements.md#fr-065) (or from another tab,
../reference/functional-requirements.md#fr-065). IDs are immutable; the list is additive.
Authentication & Users¶
- FR-001 — System MUST allow users to register with email and password (post-MVP per §39.3; MVP uses SSO only)
- FR-002 — System MUST authenticate users via JWT with 15-minute access tokens and rotatable refresh tokens
- FR-003 — System MUST allow users to log out and invalidate all active refresh tokens
- FR-004 — System MUST enforce password minimum length of 8 characters (post-MVP)
- FR-005 — System MUST support OAuth SSO via GitHub and Google at MVP
Workspaces, Projects, Environments¶
- FR-006 — Users MUST be able to create workspaces with unique slugs
- FR-007 — Users MUST be able to create projects within a workspace, with unique slugs per workspace
- FR-008 — ~~Each project MUST be pinned to exactly one cluster at creation time (§20.1)~~ (superseded 2026-07-04 by FR-077 — placement moved to the service-per-environment record; ID retained per §40's additive rule)
- FR-009 — Users MUST be able to create user-defined environments per project (e.g., production, staging, dev)
- FR-010 — System MUST isolate all environment-scoped resources (Var Groups, databases, buckets) at the environment level
- FR-011 — Production environment protection flag (§15) MUST block non-admin deploys by default
Services & Deployment¶
- FR-012 — Users MUST be able to create services linked to GitHub repositories
- FR-013 — Users MUST be able to configure which branch deploys to which environment
- FR-014 — System MUST trigger builds automatically on Git push to configured branches (debounced, §16.11)
- FR-015 — System MUST build container images using Railpack (primary) or Dockerfile (fallback) via Depot SaaS
- FR-016 — System MUST authenticate with GitHub using installation tokens when cloning private repositories
- FR-017 — System MUST generate fresh GitHub installation tokens per build to ensure validity
- FR-018 — System MUST deploy built images to Kubernetes with zero-downtime rolling updates
- FR-019 — Users MUST be able to view deployment history and rollback to any previous successful version
- FR-020 — System MUST provide customer-configurable health checks as a single input (path + timeout), rendered as startup/readiness/liveness probes per the §20.3 mapping; web services without a configured check MUST default to a TCP-connect readiness check (never ready-at-process-start)
- FR-021 — System MUST display deployment progress with distinct steps: clone, build, push, deploy (§16.12)
- FR-022 — System MUST display public URL after successful deployment
- FR-023 — System MUST support user-initiated build cancellation in
pending,building, ordeployingstates
Databases (Managed Primitive)¶
- FR-024 — Users MUST be able to provision PostgreSQL databases per environment
- FR-025 — Users MUST be able to provision Redis/Valkey instances per environment
- FR-026 — System MUST automatically inject database credentials as environment variables via system Secret
- FR-027 — System MUST display database connection strings and credentials after provisioning, subject to RBAC
- FR-028 — System MUST encrypt database credentials at rest in Postgres using AES-256-GCM
Object Storage (Managed Primitive)¶
- FR-029 — Users MUST be able to provision S3-compatible object storage buckets per environment
- FR-030 — System MUST automatically inject storage credentials (endpoint, access key, secret key) as environment variables via system Secret
- FR-031 — System MUST display storage endpoint and credentials after bucket creation, subject to RBAC
- FR-032 — Users MUST be able to configure buckets as public or private
- FR-033 — System MUST encrypt storage credentials at rest in Postgres using AES-256-GCM
Environment Variables & Secrets (Var Groups)¶
- FR-034 — Users MUST be able to create, read, update, and delete Var Groups per project
- FR-035 — Users MUST be able to attach Var Groups to services on a per-environment basis
- FR-036 — System MUST encrypt all Var Group entry values at rest using AES-256-GCM
- FR-037 — System MUST distinguish between variable entries (plaintext in UI) and secret entries (masked in UI)
- FR-038 — System MUST translate each attached Var Group into a separate K8s Secret (§38.4)
- FR-039 — System MUST display a warning when a Var Group attachment creates a key collision (§38.3)
- FR-040 — System MUST trigger a rolling pod restart when any attached Var Group value changes (via checksum annotation, §24B.2)
GitHub Integration¶
- FR-041 — Users MUST be able to install the Starform GitHub App on their accounts or organizations
- FR-042 — System MUST store GitHub installation IDs to access repositories
- FR-043 — System MUST process GitHub push webhooks with response within 10 seconds
- FR-044 — System MUST verify webhook signatures with HMAC-SHA256 before processing
- FR-045 — System MUST handle installation lifecycle events (created, deleted, suspended, unsuspended)
- FR-046 — System MUST detect and handle stale or revoked installations gracefully, marking affected services as
broken - FR-047 — System MUST deduplicate webhook deliveries by
X-GitHub-Deliveryheader (24-hour retention) - FR-048 — System MUST handle GitHub API rate limits (5000 req/hour per installation) with exponential backoff
Observability¶
- FR-049 — Users MUST be able to view real-time logs via live streaming (WebSocket or SSE from ClickHouse). Build logs are outside ClickHouse (2026-07-09): live view relays the Worker's build stream; completed-build logs replay from the per-build DO Spaces archive (§16.6)
- FR-050 — Users MUST be able to view CPU, memory, and network usage metrics per service (cAdvisor
container_cpu_*/container_memory_*/container_network_*, scraped by the OTel Collector into ClickHouse — v2 Read & query) - FR-051 — System MUST retain logs and metrics per plan: 7 days (Hobby), 30 days (Pro), 90 days (Enterprise). Implementation (v2): retention derives from the customer's plan, which Starbase owns, and is applied at the ClickHouse store — Starbase maintains a
project_id → retention_daysmapping (a ClickHouse dictionary sourced from a local table it upserts); each row computes its ownretention_daysat insert viadictGetOrDefault(…, 90)in aMATERIALIZEDcolumn, and a row-levelTTL Timestamp + toIntervalDay(retention_days)expires it — uniform for logs and metrics, no per-tenant label carried through the pipeline (Observability v2 · Store). (The superseded v1 two-store design set this per-store — logs via aretention_dayscolumn, metrics a global 90 days — see the v1 overview.) - FR-052 — All Shuttle-created resources MUST carry the label set defined in §24
- FR-053 — All Shuttle-created resources MUST carry the annotation set defined in §24B
Billing¶
- FR-054 — System MUST track pod-level usage via Shuttle Snapshot Runnable every 60s (§19)
- FR-055 — System MUST apply plan credit ($10 Hobby / $30 Pro) before charging usage overage
- FR-056 — System MUST expire unused credits monthly (Railway-style)
- FR-057 — Users MUST be able to view current estimated monthly cost with breakdown by resource category
- FR-058 — System MUST bill build minutes at $0.05/min against plan credit (§16.7)
Security¶
- FR-059 — System MUST enforce rate limiting of 100 requests per minute per user (pre-launch-blocking, §39.2 #23 — not initial-MVP)
- FR-060 — System MUST encrypt all sensitive data at rest: app-level fields (Var Group values, credentials, tokens, kubeconfigs) use AES-256-GCM in Postgres (§39.1 #18 catalog); customer-cluster etcd (Shuttle-rendered Secrets) is encrypted at rest via Talos LUKS2 disk encryption (AES-XTS-256) enabled at bootstrap, with secretbox
EncryptionConfigurationas pre-launch defense-in-depth (§39.2 #68) (customer-etcd layer clarified 2026-07-06) - FR-061 — System MUST enforce TLS 1.2 minimum with TLS 1.3 enabled on public edge endpoints (Cloudflare zone minimum-TLS setting, §5); internal telemetry transport is governed by FR-066 / FR-071 (reworded 2026-07-05 — was "TLS 1.3 for all external communications", which no edge provider defaults to and which would reject older clients of customer apps)
- FR-062 — System MUST enforce NetworkPolicies between environments within a project namespace (§20.4)
Observability & Platform Operations (v1.9 additions)¶
- FR-063 — System MUST provide per-service L7 metrics — request latency (p50/p95/p99), request rate (RPS), error rate (5xx ÷ total), and throughput — sourced from Envoy Gateway access logs (one row per request in
otel_http; computed as SQL at read — v2 Read & query). (Complements FR-050's CPU/memory.) (mechanism updated to v2 2026-07-11 — was sourced from the Envoy stats scrape) - FR-064 — System MUST attribute every customer metric and log line to the tenant key
project_id+environment+service_id— pod-origin rows via the collector'sk8sattributeslabel extraction, L7 request rows via the HTTPRoute name split once in the store DDL (v2 Collect). Workspace-boundary scoping is resolved server-side via Starbase's project→workspace map (expandworkspace_id→ itsproject_ids, filter on those) —workspace_idis a resource label (§24.1) and rides on billing snapshots (§25.2), but is not carried on telemetry rows (mechanism updated to v2 2026-07-11 — was the KSM label allowlist + series labels; clarified 2026-07-06) (§24.1, §20.2) - FR-065 — System MUST scope all customer metric and log queries by a tenant filter injected server-side from the authenticated session; the client MUST NOT be able to supply or override it
- FR-066 — System MUST ship customer telemetry over the private network (intra-region VPC) authenticated with a per-cluster bearer token validated at the regional OTel Collector gateway — both ingest doors (v2 Collect) (reworded to v2 2026-07-11 — was "at the metrics and logs ingress (§35.4)"; same requirement, one gateway)
- FR-067 — Ephemeral (preview) environments — identified by the
is_ephemeralflag (structural, not name-matched) — MUST receive the customer metrics like any environment (competitor parity; Railway/Render expose preview observability). v2 removes the series-cardinality constraint this FR originally carried: L7 metrics are rows inotel_http, not active series, so preview churn has no cardinality cost; row volume is bounded by per-plan retention (FR-051) (reworded to v2 2026-07-11 — was "MUST constrain Envoy per-route metric cardinality (stat-inclusion matcher, custom histogram buckets)", a v1 scrape-path requirement; prior rewording 2026-07-06) (v2 Store, §39.1) - FR-068 — System MUST install metrics-server at cluster bootstrap to expose the Kubernetes Metrics API for HPA and
kubectl top(self-installed on Talos) (§26.3) - FR-069 — System MUST monitor its own platform components (Starbase API/Worker, control-plane Postgres, the telemetry droplet's ClickHouse + OTel gateway, Envoy Gateway, Load Balancers, Shuttle) and the self-managed Kubernetes substrate on customer clusters (etcd, kube-apiserver, Cilium, node health, etcd-backup job age) via Grafana Cloud (hosted metrics + Grafana Alerting + OnCall), with a dedicated per-cluster Grafana Alloy agent — on every customer cluster and the DOKS platform cluster — shipping a paging keep-list of the platform series (separate from the customer OTel pipeline), including a per-cluster liveness series alerted on absence, at MVP; the telemetry-droplet (ClickHouse + OTel gateway) scrape lands pre-launch (§39.2 #64) — the MVP set is the compute components + substrate; customer identifiers MUST be excluded from the platform series, or an EU-residency stack used (§35.5)
- FR-070 — System MUST validate environment names as an RFC 1123 label (lowercase
[a-z0-9-], start/end alphanumeric, ≤30 chars) — the name is load-bearing in HTTPRoute naming (§20.2) and as a K8s label value (§24.1) - FR-071 — (the cross-region read path — built but dormant at single-region MVP; it activates when a 2nd region is added, post-MVP geographic expansion. At MVP Starbase reads its one region's stores over the intra-region VPC peering — the control-plane VPC and the regional VPC are separate even at single-region MVP; only cross-region reads are dormant. Clarified 2026-07-06 — was untagged, reading as an active MVP MUST.) When telemetry reads cross regions, the System MUST serve them over the private network (DO VPC peering) through an authenticated store front-door (a read-only ClickHouse user — the single front door) — never a public endpoint — and MUST apply the server-side tenant filter on that read path (§4.4, v2 Read & query; complements FR-065 / FR-066). (Forward design, settled 2026-07-10: transport is a per-region choice behind the
QueryTransportseam (§13) — DO regions keep this direct private read path permanently; regions on clouds without DO peering read over the Mass Relay outbound query channel — an agent on the telemetry droplet dials out to Starbase and executes the filtered queries locally — which also serves as the emergency transport for a peered region during a prolonged peering outage. On every path the stores expose no public endpoint and the server-side tenant filter is unchanged. §39.3 #43.)
Deployment Status & Deletion Safety (July 2026 additions)¶
- FR-072 — Shuttle MUST report per-service rollout status —
progressing/complete/failed, replica counts (desired/updated/ready/available), failure reason, and the observed image digest — within 15 seconds of an observed change (§25.4); Starbase MUST derive thedeploying → deployed | failedbuild-state transitions from these reports (§16.12) - FR-073 — Shuttle MUST NOT delete a managed entity because it is absent from the desired-state payload; entity deletion MUST be driven by an explicit tombstone (
deleted: true) and confirmed back via §25.4. Shuttle MUST skip all deletions in a tick whose tombstone count exceeds the configured threshold unless the payload carries an explicitforce_gcoverride, and MUST refuse any desired-state payload whosesequenceregresses (§20.3, §25.1, §27)
Managed Data Tiers (July 2026 additions)¶
- FR-074 — Database and cache instances MUST be provisioned at a customer-chosen DB tier; the tier alone determines the engine — Mininova = containerized throwaway, Micronova and above = dedicated DO Managed. Billing plans MUST NOT gate tier choice. Throwaway instances MUST carry not-for-production labeling in the dashboard, and ephemeral (
is_ephemeral) environments MUST use the throwaway tier (Starbase › Managed Databases) - FR-075 — Access to a dedicated (Micronova+) database/cache instance MUST be restricted to its owning environment, at two layers: (1) the DatabaseProvider MUST restrict DB-side network access to the nodes of the cluster(s) hosting the environment's services, using that cloud's native mechanism (DO adapter: Trusted Sources scoped to the cluster/node tag; other clouds: security groups / IP allowlists); (2) the per-environment Cilium egress allowlist — the per-tenant boundary, enforced pre-SNAT — so only that environment's pods may reach the instance endpoint (§4.4, §20.2) (reworded cloud-neutral 2026-07-11 — was written in DO product terms, unsatisfiable on other clouds)
- FR-076 — Dedicated-tier database/cache usage MUST be metered per-minute from the Starbase provisioning ledger (
created_at→deleted_at) and MUST NOT depend on Shuttle pod snapshots; throwaway (Mininova) instances bill through pod snapshots like any pod (§36, FR-054)
Placement, Networking & Durability (July 2026 additions)¶
- FR-077 — Region/cluster placement MUST be chosen per service, per environment (
cluster_idon the service-per-environment record), with a per-project default region applied unless the customer overrides; billing plans MUST NOT gate placement. Supersedes FR-008 (§20.1) - FR-078 — The platform provides no private networking across regions (customer regional VPCs are not peered to each other); the UI MUST disclose this. Cross-region service-to-service traffic goes over public hostnames. Databases are region-local (dedicated instances are VPC-private; Mininova instances are in-cluster pods); the UI MUST warn when a service is placed away from its environment's databases (§20.1, §4.4)
- FR-079 — Cross-environment networking MUST be opt-in per project (
allow_cross_env_networkingin desired state, default off), implemented solely as a NetworkPolicy scope change rendered by Shuttle; enabling it while any environment in the project is protected MUST trigger a warning (§20.4, §15.5) - FR-080 — Every pod-origin customer telemetry row (app logs, cAdvisor meters) MUST carry
cluster_id, stamped from the §24.1 label by the collector'sk8sattributes— required because the same namespace name exists on every cluster a project touches (§20.1). L7 request rows are keyed by the HTTPRoute name (§20.2), which placement makes unambiguous (a service-environment lives on exactly one cluster) (reworded to v2 2026-07-11 — was "every metric series carries acluster_idexternal label; joins and recording rules key on(cluster_id, namespace, pod)" — v2 has no cross-metric joins; identity is columns) (v2 Collect, §20.1) - FR-081 — Shuttle MUST keep a bounded in-memory buffer of undelivered snapshot batches and replay them on reconnect (ingestion remains idempotent by
snapshot_id); billing loss during a control-plane outage is bounded by the buffer depth, not the outage duration (§19.4, §36.3)
Workload Isolation & Substrate (July 2026 additions)¶
- FR-082 — Customer application pods (web, worker) MUST run under the gVisor RuntimeClass (
runtimeClassName: gvisor); platform components and Starform-rendered database pods run under runc (Kubernetes Runtime, §20.2) - FR-083 — Every customer pod MUST carry the hardening baseline:
seccompProfile: RuntimeDefault, drop all capabilities then add back onlyCHOWN,SETUID,SETGID,FOWNER,DAC_OVERRIDE,NET_BIND_SERVICE,allowPrivilegeEscalation: false,automountServiceAccountToken: false, and no host namespaces or host paths (§20.2, Security & Isolation) - FR-084 — Before public launch the untrusted tier MUST enforce abuse controls: a valid-payment gate, per-pod CPU/memory limits, and mining/abuse detection (§39.2)
- FR-085 — Machine-lifecycle management of customer clusters MUST be performed by a per-region agent that pulls desired state outbound (Shipyard); the central control plane MUST NOT require inbound access to node machine APIs (Kubernetes Runtime)
Deploy Vertical (July 2026 additions)¶
- FR-086 — When a service configures a pre-deploy command, the System MUST run it exactly once per deployment as a one-shot Job (new image, the environment's Var Groups) and MUST gate the rollout on its success; on failure or timeout the previous version MUST remain serving (§20.3, §25.4
PreDeployFailed)
Billing Calculation (August 2026 addition)¶
- FR-087 — Billing MUST price at snapshot ingest, Starbase-side: only
phase = Runningsnapshots bill (one Running snapshot = one billable pod-minute); the per-minute rate MUST be the pod's instance-size monthly price ÷ (days-in-month × 1440) — calendar-month normalized, so a pod running the full month bills exactly list price; cents MUST be resolved in the same transaction as the idempotent raw insert and accumulated inusage_totals(fractional cents; rounded half-up once at invoice) — Shuttle payloads MUST carry no pricing. A missed snapshot bills nothing: outages MUST only ever under-charge (Billing › Calculation; FR-054, FR-076, FR-081)