AOC Observability Guide

Audience: Observability Guild, Concelier/Excititor SREs, platform operators.
Scope: Metrics, traces, logs, dashboards, and runbooks introduced as part of the Aggregation-Only Contract (AOC) rollout (Sprint 19).

This guide captures the canonical signals emitted by Concelier and Excititor once AOC guards are active. It explains how to consume the metrics in dashboards, correlate traces and logs for incident triage, and operate in offline environments. Pair this guide with the AOC reference and the platform architecture overview.


1 · Metrics

MetricTypeLabelsDescription
ingestion_write_totalCountersource, tenant, result (ok, reject, noop)Counts write attempts to advisory_raw/vex_raw. Rejects correspond to guard failures.
ingestion_latency_secondsHistogramsource, tenant, phase (fetch, transform, write)Measures end-to-end runtime for ingestion stages. Use quantile=0.95 for alerting.
aoc_violation_totalCountersource, tenant, code (ERR_AOC_00x)Total guard violations bucketed by error code. Drives dashboard pills and alert thresholds.
ingestion_signature_verified_totalCountersource, tenant, result (ok, fail, skipped)Tracks signature/checksum verification outcomes.
advisory_revision_countGaugesource, tenantSupersedes depth for raw documents; spikes indicate noisy upstream feeds.
verify_runs_totalCountertenant, initiator (ui, cli, api, scheduled)How many stella aoc verify or /aoc/verify runs executed.
verify_duration_secondsHistogramtenant, initiatorRuntime of verification jobs; use P95 to detect regressions.

1.1 Alerts

1.2 · /obs/excititor/health

GET /obs/excititor/health (scope vex.admin) returns a compact snapshot for Grafana tiles and Console widgets:

{
  "generatedAt": "2025-11-08T11:00:00Z",
  "ingest": { "status": "healthy", "connectors": [ { "connectorId": "excititor:redhat", "lagSeconds": 45.3 } ] },
  "link": { "status": "warning", "lastConsensusAt": "2025-11-08T10:57:03Z" },
  "signature": { "status": "critical", "documentsEvaluated": 120, "verified": 30, "failures": 2 },
  "conflicts": { "status": "warning", "conflictStatements": 325, "trend": [ { "bucketStart": "2025-11-08T10:00:00Z", "conflicts": 130 } ] }
}
SettingDefaultPurpose
Excititor:Observability:IngestWarningThreshold06:00:00Connector lag before ingest.status becomes warning.
Excititor:Observability:IngestCriticalThreshold24:00:00Connector lag before ingest.status becomes critical.
Excititor:Observability:LinkWarningThreshold00:15:00Maximum acceptable delay between consensus recalculations.
Excititor:Observability:LinkCriticalThreshold01:00:00Delay that marks link status as critical.
Excititor:Observability:SignatureWindow12:00:00Lookback window for signature coverage.
Excititor:Observability:SignatureHealthyCoverage0.8Coverage ratio that still counts as healthy.
Excititor:Observability:SignatureWarningCoverage0.5Coverage ratio that flips the status to warning.
Excititor:Observability:ConflictTrendWindow24:00:00Rolling window used for conflict aggregation.
Excititor:Observability:ConflictTrendBucketMinutes60Resolution of conflict trend buckets.
Excititor:Observability:ConflictWarningRatio0.15Fraction of consensus docs with conflicts that triggers warning.
Excititor:Observability:ConflictCriticalRatio0.3Ratio that marks conflicts.status as critical.
Excititor:Observability:MaxConnectorDetails50Number of connector entries returned (keeps payloads small).

1.3 · Regression & DI hygiene

  1. Keep storage/integration tests green when telemetry touches persistence.
    • ./tools/postgres/local-postgres.sh start downloads PostgreSQL 16.x (if needed), launches the instance, and prints export EXCITITOR_TEST_POSTGRES_URI=postgresql://.../excititor-tests. Copy that export into your shell.
    • ./tools/postgres/local-postgres.sh restart is a shortcut for “stop if running, then start” using the same dataset—use it after tweaking config or when tests need a bounce without wiping fixtures.
    • ./tools/postgres/local-postgres.sh clean stops the instance (if running) and deletes the managed data/log directories so storage tests begin from a pristine catalog.
    • Run dotnet test src/Excititor/__Tests/StellaOps.Excititor.Storage.Postgres.Tests/StellaOps.Excititor.Storage.Postgres.Tests.csproj -nologo -v minimal (add --filter if you only touched specific suites). These tests exercise the same write paths that feed the dashboards, so regressions show up immediately.
    • ./tools/postgres/local-postgres.sh stop when finished so CI/dev hosts stay clean; status|logs|shell are available for troubleshooting.
  2. Declare optional Minimal API dependencies with [FromServices] ... = null. RequestDelegateFactory treats [FromServices] IVexSigner? signer = null (or similar) as optional, so host startup succeeds even when tests have not registered that service. This pattern keeps observability endpoints cancellable while avoiding brittle test overrides.

2 · Traces

2.1 Span taxonomy

Span nameParentKey attributes
ingest.fetchjob root spansource, tenant, uri, contentHash
ingest.transformingest.fetchdocumentType (csaf, osv, vex), payloadBytes
ingest.writeingest.transformcollection (advisory_raw, vex_raw), result (ok, reject)
aoc.guardingest.writecode (on violation), violationCount, supersedes
verify.runverification job roottenant, window.from, window.to, sources, violations

2.2 Trace usage

2.3 Telemetry configuration (Excititor)


3 · Logs

Structured logs include the following keys (JSON):

KeyDescription
traceIdMatches OpenTelemetry trace/span IDs for cross-system correlation.
tenantTenant identifier enforced by Authority middleware.
source.vendorLogical source (e.g., redhat, ubuntu, osv, ghsa).
upstream.upstreamIdVendor-provided ID (CVE, GHSA, etc.).
contentHashsha256: digest of the raw document.
violation.codePresent when guard rejects ERR_AOC_00x.
verification.windowPresent on /aoc/verify job logs.

Excititor APIs mirror these identifiers via response headers:

HeaderPurpose
X-StellaOps-TraceIdW3C trace/span identifier for deep-linking from Console → Grafana/Loki.
X-Stella-CorrelationIdStable correlation identifier (respects inbound header or falls back to the request trace ID).

Logs are shipped to the central Loki/Elasticsearch cluster. Use the template query:

{app="concelier-web"} | json | violation_code != ""

to spot active AOC violations.

3.1 · Advisory chunk API (Advisory AI feeds)

Advisory AI leans on Concelier’s /advisories/{key}/chunks endpoint for deterministic evidence packs. The service exports dedicated metrics so dashboards can highlight latency spikes, cache noise, or aggressive guardrail filtering before they impact Advisory AI responses.

MetricTypeLabelsDescription
advisory_ai_chunk_requests_totalCountertenant, result, truncated, cacheCount of chunk API calls, tagged with cache hits/misses and truncation state.
advisory_ai_chunk_latency_millisecondsHistogramtenant, result, truncated, cacheEnd-to-end build latency (milliseconds) for each chunk request.
advisory_ai_chunk_segmentsHistogramtenant, result, truncatedNumber of chunk segments returned to the caller; watch for sudden drops tied to guardrails.
advisory_ai_chunk_sourcesHistogramtenant, resultHow many upstream observations/sources contributed to a response (after observation limits).
advisory_ai_guardrail_blocks_totalCountertenant, reason, cachePer-reason count of segments suppressed by guardrails (length, normalization, character set).

Dashboards should plot latency P95/P99 next to cache hit rates and guardrail block deltas to catch degradation early. Advisory AI CLI/Console surfaces the same metadata so support engineers can correlate with Grafana/Loki entries using traceId/correlationId headers.


4 · Dashboards

Primary Grafana dashboard: “AOC Ingestion Health” (dashboards/aoc-ingestion.json). Panels include:

  1. Sources overview: table fed by ingestion_write_total and ingestion_latency_seconds (mirrors Console tiles).
  2. Violation trend: stacked bar chart of aoc_violation_total per code.
  3. Signature success rate: timeseries derived from ingestion_signature_verified_total.
  4. Supersedes depth: gauge showing advisory_revision_count P95.
  5. Verification runs: histogram and latency boxplot using verify_runs_total / verify_duration_seconds.

Secondary dashboards:

Update docs/assets/dashboards/ with screenshots when Grafana capture pipeline produces the latest renders.


5 · Operational workflows

  1. During ingestion incident:
    • Check Console dashboard for offending sources.
    • Pivot to logs using document contentHash.
    • Re-run stella sources ingest --dry-run with problematic payloads to validate fixes.
    • After remediation, run stella aoc verify --since 24h and confirm exit code 0.
  2. Scheduled verification:
    • Configure cron job to run stella aoc verify --format json --export ....
    • Ship JSON to aoc-verify bucket and ingest into metrics using custom exporter.
    • Alert on missing exports (no file uploaded within 26 h).
  3. Offline kit validation:
    • Use the Offline Mode Dashboard to confirm snapshots contain the latest metrics.
    • Run verification reports locally and attach them to the bundle before distribution.
  4. Incident toggle audit:
    • Authority requires incident_reason when issuing obs:incident tokens; plan your runbooks to capture business justification.
    • Auditors can call /authority/audit/incident?limit=100 with the tenant header to list recent incident activations, including reason and issuer.

6 · Offline considerations


7 · References


8 · Compliance checklist


Last updated: 2025-10-26 (Sprint 19).