Skip to content

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, or deploying states

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-Delivery header (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_days mapping (a ClickHouse dictionary sourced from a local table it upserts); each row computes its own retention_days at insert via dictGetOrDefault(…, 90) in a MATERIALIZED column, and a row-level TTL 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 a retention_days column, 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 EncryptionConfiguration as 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's k8sattributes label 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 (expand workspace_id → its project_ids, filter on those) — workspace_id is 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-067Ephemeral (preview) environments — identified by the is_ephemeral flag (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 in otel_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 QueryTransport seam (§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 the deploying → deployed | failed build-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 explicit force_gc override, and MUST refuse any desired-state payload whose sequence regresses (§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_atdeleted_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_id on 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_networking in 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's k8sattributes — 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 a cluster_id external 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 only CHOWN,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 = Running snapshots 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 in usage_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)