Time-to-First-Signal (TTFS) Architecture

Derived from Product Advisory (14-Dec-2025): UX and Time-to-Evidence Technical Reference; details the TTFS subsystem for providing immediate feedback on run/job status.

1) Overview

Time-to-First-Signal (TTFS) measures the latency from user action (opening a run, starting a scan, CLI invocation) to the first meaningful signal being displayed or logged. This architecture ensures users receive immediate feedback regardless of actual job completion time.

1.1 Design Goals

1.2 Signal Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                           TTFS Signal Flow                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   User Action          API Layer              Cache Layer      Data Layer   │
│   ───────────          ─────────              ───────────      ──────────   │
│                                                                             │
│   [Route Enter] ──┬──► /first-signal ───────► Valkey/Redis ─┐              │
│   [CLI Start]  ───┤        │                      │         │              │
│   [CI Job]     ───┘        │                      │         ▼              │
│                            │                      │    ┌──────────────┐    │
│                            ▼                      │    │ PostgreSQL   │    │
│                      ┌──────────┐                 │    │ first_signal │    │
│                      │ ETag     │◄────────────────┤    │ _snapshots   │    │
│                      │ Validation│                │    └──────────────┘    │
│                      └──────────┘                 │                        │
│                            │                      │                        │
│                            ▼                      ▼                        │
│                      ┌──────────────────────────────┐                      │
│                      │     Response Assembly        │                      │
│                      │ • kind (status indicator)    │                      │
│                      │ • phase (current stage)      │                      │
│                      │ • summary (human text)       │                      │
│                      │ • eta_seconds (estimate)     │                      │
│                      │ • last_known_outcome         │                      │
│                      │ • next_actions               │                      │
│                      └──────────────────────────────┘                      │
│                                    │                                        │
│                                    ▼                                        │
│                      ┌──────────────────────────────┐                      │
│                      │     SSE / Polling Client     │                      │
│                      └──────────────────────────────┘                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2) Component Budgets

The 5-second P95 budget is allocated across components:

ComponentP50 BudgetP95 BudgetNotes
Frontend (skeleton + hydration)100ms150msNetwork-independent
Edge API (auth + routing)150ms250msJWT validation, rate limiting
Core Services (lookup + assembly)700ms1,500msCache hit vs cold path
SSE/WebSocket establishment300msFallback to polling if exceeded
Total (warm path)700ms2,500msCache hit scenario
Total (cold path)1,200ms4,000msCache miss, compute required

3) Signal Kinds

The kind field indicates the current signal state:

KindDescriptionTypical DurationIcon
queuedJob waiting in queue0-30sQueue
startedJob has begun executionPlay
phaseJob in specific phaseVariesProgress
blockedWaiting on dependency/policyPause
failedJob has failedError
succeededJob completed successfullyCheck
canceledJob was canceledCancel
unavailableSignal cannot be determinedUnknown

4) Signal Phases

The phase field indicates the current execution phase:

PhaseDescriptionSLO Target
resolveDependency/artifact resolutionP95 < 30s
fetchData retrieval (registry, advisories)P95 < 45s
restoreCache/snapshot restorationP95 < 10s
analyzeAnalysis execution (scan, policy)P95 < 120s
policyPolicy evaluationP95 < 15s
reportReport generation/uploadP95 < 30s
unknownPhase cannot be determined

5) API Contracts

5.1 First Signal Endpoint

GET /api/v1/jobengine/jobs/{jobId}/first-signal
Accept: application/json
If-None-Match: "{etag}"

200 OK
ETag: "job-{id}-{updated_at.unix_ms}"
Cache-Control: private, max-age=1, stale-while-revalidate=5
X-Signal-Source: snapshot | cold_start | failure_index

{
  "kind": "started",
  "phase": "analyze",
  "summary": "Scanning image layers (47%)",
  "eta_seconds": 38,
  "last_known_outcome": {
    "status": "succeeded",
    "finished_at": "2025-12-13T10:15:00Z",
    "findings_count": 12
  },
  "next_actions": [
    {"label": "View previous run", "href": "/runs/abc-123"}
  ],
  "diagnostics": {
    "queue_position": null,
    "worker_id": "worker-7"
  }
}

304 Not Modified (if ETag matches)

5.2 SSE Stream

GET /api/v1/jobengine/stream/jobs/{jobId}/first-signal
Accept: text/event-stream

event: signal
data: {"kind":"started","phase":"analyze",...}

event: signal
data: {"kind":"phase","phase":"policy",...}

event: done
data: {"kind":"succeeded",...}

5.3 CLI Integration

# Job status with immediate signal
stella job status <job-id> --watch

# Output progression:
# [queued] Waiting in queue (position: 3)
# [started] Job started on worker-7
# [phase:analyze] Scanning image layers (47%)
# [succeeded] Completed in 2m 34s

6) Caching Strategy

6.1 Cache Tiers

TierStorageTTLUse Case
L1In-memory (per-instance)1sHot path, same-instance requests
L2Valkey/Redis5sCross-instance, active jobs
L3PostgreSQL24hPersistent snapshots, air-gap mode

6.2 Cache Keys

ttfs:job:{tenant_id}:{job_id}:signal      # Current signal
ttfs:job:{tenant_id}:{job_id}:eta         # ETA prediction
ttfs:run:{tenant_id}:{run_id}:signals     # Run-level aggregation
ttfs:tenant:{tenant_id}:failure_sig       # Failure signatures

6.3 Air-Gap Mode

In air-gapped environments without Valkey/Redis:

  1. PostgreSQL NOTIFY/LISTEN replaces pub/sub for real-time updates
  2. Polling fallback with 2-second intervals
  3. first_signal_snapshots table serves as L2 cache
  4. All SSE endpoints gracefully degrade to long-polling

7) Telemetry & Observability

7.1 Metrics

MetricTypeDescription
ttfs_latency_secondsHistogramEnd-to-end signal latency
ttfs_cache_latency_secondsHistogramCache lookup time
ttfs_cold_latency_secondsHistogramCold path computation time
ttfs_signal_totalCounterSignals by kind/surface
ttfs_cache_hit_totalCounterCache hits
ttfs_cache_miss_totalCounterCache misses
ttfs_slo_breach_totalCounterSLO breaches
ttfs_error_totalCounterErrors by type

7.2 Labels

All metrics include the following labels:

7.3 SLO Definitions

# Prometheus recording rules
- record: ttfs:slo:p50_target
  expr: 2.0  # seconds

- record: ttfs:slo:p95_target
  expr: 5.0  # seconds

- record: ttfs:slo:compliance
  expr: |
    histogram_quantile(0.95, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le))
    < 5.0

# Alerting rules
- alert: TtfsSloBreachP95
  expr: histogram_quantile(0.95, sum(rate(ttfs_latency_seconds_bucket[5m])) by (le)) > 5.0
  for: 5m
  labels:
    severity: page
  annotations:
    summary: "TTFS P95 exceeds 5s SLO"

- alert: TtfsHighErrorRate
  expr: rate(ttfs_error_total[5m]) > 0.1
  for: 2m
  labels:
    severity: warning

8) Frontend Integration

8.1 Component Hierarchy

FirstSignalCard (Smart Component)
├── FirstSignalStore (Signal-based State)
│   ├── SSE subscription
│   ├── Polling fallback
│   └── ETag caching
├── StatusIndicator (Dumb Component)
│   └── kind → icon + color mapping
├── PhaseProgress (Dumb Component)
│   └── phase → progress bar
└── ActionButtons (Dumb Component)
    └── next_actions rendering

8.2 State Machine

type FirstSignalLoadState = 'idle' | 'loading' | 'streaming' | 'error' | 'done';

// State transitions:
// idle → loading (initial fetch)
// loading → streaming (SSE connected) | error (fetch failed)
// streaming → done (terminal signal) | error (connection lost)
// error → loading (retry)

8.3 Animation Tokens

TokenValueUsage
--motion-duration-quick150msSkeleton fade, icon transitions
--motion-duration-normal250msCard expansion, phase transitions
--motion-duration-slow400msSuccess/failure celebrations
--motion-easing-standardcubic-bezier(0.4, 0, 0.2, 1)Default easing
--motion-easing-deceleratecubic-bezier(0, 0, 0.2, 1)Entries
--motion-easing-acceleratecubic-bezier(0.4, 0, 1, 1)Exits

8.4 Browser TTFS Emission

9) Failure Signatures

Failure signatures enable predictive “last known outcome” by pattern-matching historical failures.

9.1 Signature Schema

{
  "signature_hash": "sha256:abc123...",
  "pattern": {
    "phase": "analyze",
    "error_code": "LAYER_EXTRACT_FAILED",
    "image_pattern": "registry.io/.*:v1.*"
  },
  "outcome": {
    "likely_cause": "Registry rate limiting",
    "mttr_p50_seconds": 300,
    "suggested_action": "Wait 5 minutes and retry"
  },
  "confidence": 0.87,
  "sample_count": 42
}

9.2 Usage

When a job enters a known failure pattern:

  1. Match current job state against failure_signatures table
  2. Enrich signal with last_known_outcome.likely_cause
  3. Predict ETA based on historical MTTR
  4. Suggest remediation via next_actions

10) Database Schema

See docs/db/schemas/ttfs.sql for the complete schema definition.

10.1 Core Tables

TablePurpose
scheduler.first_signal_snapshotsCached signal state per job
scheduler.ttfs_eventsTelemetry event log
scheduler.failure_signaturesHistorical failure patterns

10.2 Hourly Rollup View

The scheduler.ttfs_hourly_summary view provides pre-aggregated metrics for dashboard performance.

11) Testing Requirements

11.1 Unit Tests

11.2 Integration Tests

11.3 Deterministic Fixtures

// tests/fixtures/ttfs/
export const TTFS_FIXTURES = {
  FROZEN_TIMESTAMP: '2025-12-04T12:00:00.000Z',
  DETERMINISTIC_SEED: 0x5EED2025,
  SAMPLE_JOB_ID: '550e8400-e29b-41d4-a716-446655440000',
  SAMPLE_TENANT_ID: 'tenant-test-001'
};

12) Observability

12.1 Grafana Dashboard

The TTFS observability dashboard provides real-time visibility into signal latency, cache performance, and SLO compliance.

Key panels:

12.2 Alert Rules

TTFS alerts are defined in docs/modules/telemetry/operations/alerts/ttfs-alerts.yaml.

Critical alerts:

AlertThresholdFor
TtfsP95HighP95 > 5s5m
TtfsSloBreach>10 breaches in 5m1m
FirstSignalEndpointDownOrchestrator unavailable2m

Warning alerts:

AlertThresholdFor
TtfsCacheHitRateLow<70%10m
TtfsErrorRateHigh>1%5m
FirstSignalEndpointLatencyHighP95 > 500ms5m

12.3 Load Testing

Load tests validate TTFS performance under realistic conditions.

Scenarios:

Thresholds:

13) References