Time Dimension Segmentation
Time Dimension Segmentation (TDS) is a cross-cutting architectural principle for all opensecstack platforms. It assigns every API endpoint and background job to one of three named latency tiers, prescribes the implementation pattern for each tier, and provides a tool — tds-scanner — to measure compliance at runtime. TDS is defined across the full Architecture and applies to every platform in the ecosystem.
The rationale and formal decision record are in ADR-009.
What is TDS?
opensecstack platforms handle operations whose latency requirements span many orders of magnitude — from sub-millisecond hash computations to multi-minute full-platform audit scans. Without explicit latency contracts, teams repeatedly made the same mistakes: synchronous endpoints blocking on operations that should have been async, fast operations delayed by co-located slow work, and no shared vocabulary for discussing expected response times across codebases written in Go, Rust, Python, and TypeScript.
TDS solves this by dividing operations into three tiers named after clock hands. Each tier carries an implicit latency contract and a prescribed implementation pattern. Saying "this endpoint is second-hand" communicates both the bound (<300ms) and the required pattern (synchronous HTTP response) to anyone who has read this page once.
The three tiers
| Tier | Latency bound | Implementation pattern | Example operations |
|---|---|---|---|
| Second hand | <300ms | Synchronous HTTP response | Health check, status poll, per-endpoint CVSS scoring, MARSHAL gate evaluation, AUGUR advisory fetch, control status update |
| Minute hand | 300ms – 30s | Short-lived async job with polling endpoint, or synchronous with generous timeout (acceptable for operations <10s) | HTML/PDF report generation, full scan of a small API spec (<50 endpoints), evidence artifact upload, audit log fetch, chain anchor age check |
| Hour hand | >30s | Background job with webhook callback or polling endpoint — never synchronous | Full scan of a large API spec (>200 endpoints), full compliance export, WORM chain verification (7-day window), VIGIL_DEEP full audit scan |
Prometheus alerting rules fire when a second-hand operation's P95 latency exceeds 250ms (warning) or 300ms (critical). All new API endpoints and background jobs must declare their TDS tier in the design document or ADR before a pull request can be merged.
Platform TDS assignments
APIGuard
| Component | Tier |
|---|---|
| Spec parse (Rust subprocess) | Second hand |
| Per-endpoint analysis (Go) | Second hand |
| CVSS scoring | Second hand |
| Scan status API | Second hand |
| HTML report generation | Minute hand |
| PDF report generation | Minute hand |
| Full scan — small spec (<50 endpoints) | Minute hand |
| Full scan — large spec (>200 endpoints) | Hour hand |
NIS2 Compass
| Component | Tier |
|---|---|
| Control status update | Second hand |
| Organisation CRUD | Second hand |
| Evidence artifact upload | Minute hand |
| Audit log fetch | Minute hand |
| Full compliance export | Hour hand |
CITADEL
| Component | Tier |
|---|---|
| MARSHAL gate evaluation | Second hand |
| AUGUR advisory fetch | Second hand |
| VIGIL_REALTIME status poll | Second hand |
| Chain anchor age check | Minute hand |
| WORM chain verify (7-day window) | Hour hand |
| VIGIL_DEEP full audit scan | Hour hand |
Implementation patterns
Second-hand — synchronous handler
Must complete at P95 within 300ms. Avoid external HTTP calls (use cached data), full table scans without indexes, subprocess invocations longer than ~50ms, and loops over unbounded datasets.
"cm">// TDS: second-hand func (h *Handler) GetVIGILStatus(w http.ResponseWriter, r *http.Request) { status, err := h.vigil.CurrentStatus(r.Context()) if err != nil { http.Error(w, err.Error(), 500) return } json.NewEncoder(w).Encode(status) }
Minute-hand — async job with polling
"cm">// TDS: minute-hand — Pattern B: async job with polling "cm">// POST /api/v1/reports — starts job, returns job ID immediately func (h *Handler) StartReport(w http.ResponseWriter, r *http.Request) { jobID := h.jobs.Enqueue(reportJob) w.WriteHeader(http.StatusAccepted) json.NewEncoder(w).Encode(map[string]string{"job_id": jobID}) } "cm">// GET /api/v1/reports/{job_id} — poll for result func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) { job, _ := h.jobs.Get(jobID) if job.Status == "pending" { w.WriteHeader(http.StatusAccepted) return } json.NewEncoder(w).Encode(job.Result) }
Hour-hand — background job, never synchronous
"cm">// TDS: hour-hand "cm">// POST /api/v1/vigil/deep-scan — enqueues scan, returns immediately func (h *Handler) TriggerDeepScan(w http.ResponseWriter, r *http.Request) { scanID := h.deepScanner.Enqueue(DeepScanJob{Period: period}) w.WriteHeader(http.StatusAccepted) json.NewEncoder(w).Encode(map[string]string{ "scan_id": scanID, "poll_url": fmt.Sprintf("/api/v1/vigil/deep-scan/%s", scanID), "webhook_on": "vigil_deep_completed", }) }
Cross-tier decomposition
Some operations have a fast trigger but slow execution. Decompose them so the caller always gets a fast response and the slow work runs in the background:
"cm"># Second-hand: POST /api/v1/scans → {scan_id, status: "queued"} "cm"># Second-hand: GET /api/v1/scans/{id} → current status and progress "cm"># Hour-hand: [background] scan execution → updates status as it runs "cm"># Second-hand: GET /api/v1/scans/{id}/findings → findings once complete
TripleHash alignment
The CITADEL TripleHash scheme (BLAKE3 + SHA-256 + SHA-512) maps directly to TDS tiers, making the relationship between cryptographic assurance level and operational latency explicit:
| Hash algorithm | TDS tier | Use |
|---|---|---|
| BLAKE3 | Second hand | Per-request real-time integrity checks |
| SHA-256 | Minute hand | WORM chain hashing |
| SHA-512 | Hour hand | Long-term archival and Ed25519 anchor signing |
hash := vantage_hash.TripleHash.Compute(content) "cm">// Second-hand: real-time integrity realtimeCheck := hash.Blake3Hex() "cm">// Minute-hand: WORM chain hashing chainHash := hash.SHA256Hex() "cm">// Hour-hand: archival / anchor signing archivalHash := hash.SHA512Hex()
tds-scanner
tds-scanner is a CLI tool in sdk/tools/tds-scanner/ that measures actual operation latencies against tier bounds. It is the standard way to verify TDS compliance for a running deployment.
Installation
go install github.com/opensecstack/sdk/tools/tds-scanner@latest
"cm"># Or build from source
cd sdk/tools/tds-scanner
go build -o tds-scanner .Running a scan
tds-scanner scan \ --target https://your-platform.internal \ --api-key $KEY \ --platform apiguard "cm"># apiguard | nis2compass | citadel --iterations 5 "cm"># median of 5 runs per operation (default) --output text "cm"># text | json | junit
Key flags
| Flag | Default | Description |
|---|---|---|
--target | — | Platform base URL (required) |
--api-key | — | API key for the platform (required) |
--platform | apiguard | Which platform's operation set to test |
--iterations | 5 | Runs per operation; median is reported |
--output | text | Output format: text, json, or junit |
--fail-on-violation | false | Exit code 1 if any TDS violation is found |
--timeout | 300s | Maximum total scan duration |
A TDS violation does not mean the system is broken — it means an operation is taking longer than its tier contract allows. Common causes for second-hand violations are missing database indexes, network latency between scanner and platform, or the platform running under load.
JSON output format
{
"scan_id": "uuid",
"ts_utc": "2026-03-30T14:00:00Z",
"platform": "apiguard",
"target": "https:class=class="str">"cm">//apiguard.internal",
"operations": [
{
"name": "scan_start",
"tier": "second-hand",
"tier_bound_ms": 300,
"median_ms": 87,
"p95_ms": 102,
"status": "PASS"
}
],
"summary": {
"total": 9,
"pass": 9,
"fail": 0,
"tds_compliant": true
}
}CI/CD integration
Run tds-scanner in CI with --fail-on-violation to prevent TDS regressions from reaching production. The junit output format is compatible with GitHub Actions, GitLab CI, and Jenkins.
- name: TDS compliance scan run: | tds-scanner scan \ --target ${{ secrets.APIGUARD_URL }} \ --api-key ${{ secrets.APIGUARD_KEY }} \ --output junit \ --fail-on-violation \ > tds-results.xml - name: Publish TDS results uses: mikepenz/action-junit-report@v4 with: report_paths: tds-results.xml
To capture a latency baseline for regression comparison use tds-scanner baseline and pass --compare-baseline baseline.json on subsequent scans.