Prometheus
DE EN

06 — Datenmodell

Aufbau eines Datensatzes

Alles, was Prometheus speichert und veröffentlicht, ist eine Zeile derselben Form: welches Projekt, welche Version, welche Kohorte, welches Zeitfenster — und ein Wert. Aus den Melder-Modi A bis C wird dieser Wert als Band ausgewiesen statt als Zahl; nur Modus D führt Zahlen aus öffentlichen Registries im Klartext. Das Schema hat keine Felder für Personen, Hosts oder Sitzungen — sie sind nicht etwa leer, es gibt sie schlicht nicht.

Schlüssel

Der Aggregat-Key

(project_id, version_id, cohort_id, window_start) — mehr braucht es nicht, um ein Aggregat zu identifizieren. Was darin nicht vorkommt, ist genauso Teil der Aussage wie das, was darin steht.

So entsteht eine Zeile: Aus Roh-Events, die nur im Arbeitsspeicher liegen, werden vier Schlüsselspalten gesetzt, der gemessene Wert wird auf ein Band gemappt, und erst nach der k-Prüfung verlässt das Ergebnis die Maschine.

  1. project_id pkg:npm/example-lib
  2. version_id 1.4.2
  3. cohort_id rt-runtime-mins
  4. window_start 2026-05-26T12:00:00Z
value 47 30-60 Die exakte Minutenzahl bleibt lokal; publiziert wird das Band.
k_effective 23 ≥ k_min 5 PASS
Beispielwerte. Läge k_effective unter 5, würde dieselbe Zeile lokal nach __small_cohorts umgeschrieben — vor dem Senden, nicht danach.

project_id — die PURL

Immer eine Package-URL, nie freier Text: pkg:npm/example-lib, pkg:pypi/sample-pkg, pkg:maven/<group>/<artifact>, pkg:cargo/…, pkg:golang/…. Damit ist pkg:npm/foo sauber von pkg:pypi/foo getrennt.

version_id — die gemessene Version

Die Version, auf die sich die Messung bezieht. Erst dadurch wird die Frage beantwortbar, ob eine Migration in der Breite ankommt oder ob eine alte Major-Version weiterläuft.

cohort_id — die Kategorie

Eine vordefinierte Kategorie, kein frei gebildeter Schlüssel — etwa rt-runtime-mins, install-count oder dep-edges-from-public-manifests. Neue Kohorten werden im Federation-Standard publiziert, nicht ad hoc erfunden.

window_start — das Zeitfenster

Beginn eines Tumbling-Fensters, UTC-ausgerichtet. Feinere Zeitstempel als die Fenstergranularität sind im Schema verboten — sie wären ein Verhaltensprofil.

Was kein Teil des Keys ist: Es gibt kein tenant_id — die Plattform kennt keine Mandanten, weil sie keine Kunden hat. Und source_mode (A, B, C, D) ist eine eigene Spalte, kein Schlüsselbestandteil: dieselbe Kohorte aus dem SDK und aus dem Registry-Scraper steht bewusst nebeneinander, statt sich gegenseitig zu überschreiben. Der Key gruppiert also, er erzwingt keine Einzigkeit — zwei Zeilen mit gleichem Key und verschiedenem source_mode sind zwei Messungen derselben Sache aus zwei Quellen.

Wire-Format

Aufbau einer Submission

Eine Submission ist ein kanonisch serialisiertes JSON-Dokument mit einem Fenster, einer Melder-Identität, einer Signatur und einer Liste von Aggregaten. Was hier nicht drinsteht, bekommt ein Mirror auch nicht vom Melder — seine eigenen technischen Metadaten und Bewertungen setzt er beim Eingang selbst dazu.

{
  "schema_version": "1.0",
  "submitter_id":   "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
  "signature":      "ed25519:5K3PqL…",
  "window_start":   "2026-05-26T12:00:00Z",
  "window_end":     "2026-05-26T13:00:00Z",
  "aggregates": [
    {
      "project_id":  "pkg:npm/example-lib",
      "version_id":  "1.4.2",
      "cohort_id":   "rt-runtime-mins",
      "metric":      "runtime_minutes_band",
      "value":       "30-60",      // Band, keine Zahl
      "k_effective": 23,           // tatsächliche Kohortengröße
      "k_min":       5,            // geforderte Untergrenze
      "source_mode": "A"            // Erfassungsmodus, Spalte statt Key
    }
  ],
  "attestation": {
    "kind":   "declarative",
    "claims": ["k>=5", "no-pii-fields", "window-monotonic"]
  }
}

Das Beispiel ist eine Lese-Ansicht der Struktur, nicht die signierte Byte-Folge: Kanonisches JSON kennt keine //-Kommentare, und JCS sortiert die Keys lexikografisch. Signiert wird das Ergebnis dieser Kanonisierung, nicht das, was hier steht.

Kanonische Bytes — RFC 8785

Keys sortiert, keine Whitespace-Varianten, UTF-8. Dieselbe Eingabe ergibt in jeder SDK-Sprache byte-identisch dieselbe Ausgabe — sonst wäre keine Signatur sprachübergreifend prüfbar.

Signatur — ed25519 ohne Prehash

Signiert wird direkt über die kanonischen Body-Bytes. Die Melder-Identität ist ein did:key: ein selbstbeschreibender Schlüssel, keine Registrierung bei irgendeiner Stelle.

submission_hash — Idempotenz

sha256 über denselben kanonischen Body, aber getrennt von der Signatur und kein Signatur-Input. Er ist der Schlüssel im öffentlichen Audit-Log: dieselbe Submission zweimal eingereicht erzeugt keinen zweiten Eintrag.

attestation — die Zusicherung

Der Melder erklärt, welche Bedingungen er lokal eingehalten hat. In Phase 1 ist die Zusicherung deklarativ; ab Phase 2 tritt eine kryptografische k-Anon-Attestation an ihre Stelle.

Felder

Die Felder im Einzelnen

Die obere Gruppe kommt in den Modi A, B und C vom Melder und ist Teil der signierten Bytes. Bei Modus D gibt es keinen Melder: Dort füllt der Scraper des Mirrors dieselben Spalten mirror-intern und ohne Signatur-Roundtrip. Die untere Gruppe setzt der Mirror in jedem Fall beim Eingang — sie ist Auswertung, nicht Aussage des Melders, und deshalb bewusst nicht signiert.

Alle Felder eines Aggregats mit Beispiel, Bedeutung und Herkunft
Feld Beispiel Bedeutung Gesetzt von
Vom Melder — bei Modus D vom Scraper des Mirrors
project_id pkg:npm/example-lib Gemessenes Projekt als PURL. Key-Bestandteil. Melder (A–C) · Scraper (D)
version_id 1.4.2 Gemessene Version. Key-Bestandteil. Melder (A–C) · Scraper (D)
cohort_id rt-runtime-mins Vordefinierte Kategorie. Key-Bestandteil. Melder (A–C) · Scraper (D)
window_start / window_end 2026-05-26T12:00:00Z Tumbling-Fenster, UTC-ausgerichtet. window_start ist Key-Bestandteil. Melder (A–C) · Scraper (D)
metric runtime_minutes_band Welche Größe gemessen wurde. Bestimmt, welche Bänder für value gelten. Melder (A–C) · Scraper (D)
value 30-60 Band-Label, keine Klartext-Zahl — Ausnahme ist Modus D auf öffentlichen Quellen. Melder · bei D der Scraper
k_effective 23 Tatsächliche Kohortengröße hinter dem Wert. Bei Modus D leer, weil eine öffentliche Registry keine Kohorte hat. Melder · für D nicht anwendbar
k_min 5 Geforderte Untergrenze. Default 5; in den endnutzernahen Modi B und C mindestens 25. Melder · für D nicht anwendbar
submitter_did
Wire-Feld: submitter_id
did:key:z6Mk… Identität des Melders als selbstbeschreibender Schlüssel — eine Instanz, keine Person. Melder · für D nicht anwendbar
source_mode A Erfassungsmodus A, B, C oder D. Spalte, nicht Key-Bestandteil. Melder · bei D der Scraper
Vom Mirror beim Eingang gesetzt
submission_hash 244be0…3df0 64 Hex-Zeichen über die kanonischen Bytes. Idempotenz-Schlüssel und Verweis ins Audit-Log. Mirror
received_at 2026-05-26T13:04:11Z Eingangszeit am Mirror. Bewusst getrennt vom Messfenster. Mirror
trust_tier newestablished Neue Melder durchlaufen einen siebentägigen Burn-In, bevor ihre Daten als etabliert gelten. Mirror
plausibility ok Auffälligkeits-Marker (track-a-spike, track-d-missing). Nichts wird gelöscht, es wird annotiert. Mirror
provenance registry-scraper@mirror-eu-1 Herkunft bei Modus D: welcher Scraper bei welchem Mirror die Zeile erzeugt hat. Mirror
source_schema_version 1.0 Major-Version des Submission-Schemas, aus dem die Zeile stammt. Mirror

Die vier Annotations-Spalten — trust_tier, plausibility, provenance und source_schema_version — sind Auswertungs-Output und im Wire-Format schlicht nicht vorgesehen: Ein Melder kann sich weder selbst als vertrauenswürdig einstufen noch seine eigene Herkunft umschreiben.

Fenster & Bänder

Banding statt exakter Zahlen

Die Auflösung ist bewusst so grob, dass niemand darin auftaucht. Zwei Stellschrauben entscheiden darüber, ob aus Aggregaten ein Profil rekonstruierbar wird: wie fein die Zeit aufgelöst ist und wie genau der Wert beziffert wird. Beide sind bewusst grob gewählt.

Fenstergrößen und wo sie sichtbar werden
Fenster Verwendung Sichtbar
1 min nur lokal im Aggregator, zum Debuggen nie in einer Submission
1 h Standard für das Self-Instrumentation-SDK (Modus A) Submission, API, Snapshot
1 d Standard für das Registry-Scraping (Modus D) mirror-interner Datensatz, API, Snapshot
Bänder je Kohorte und Metrik
Kohorte / Metrik Bänder
rt-runtime-mins
runtime_minutes_band
0-5 · 5-15 · 15-30 · 30-60 · 60-180 · 180-1440 · 1440+
install-count
install_count_band
1 · 2-5 · 5-25 · 25-100 · 100-1000 · 1000+
dep-edges-from-public-manifests
observed_dependency_count
Klartext-Zahl zulässig — Modus D wertet ausschließlich öffentliche Manifeste aus, hier gibt es nichts zu schützen, was nicht ohnehin öffentlich wäre.

So sieht dieselbe Struktur aus, wenn sie aus dem Registry-Scraping kommt: kein k_effective, weil es keine Kohorte gibt, dafür eine Herkunftsangabe und ein Tagesfenster.

{
  "project_id":  "pkg:pypi/sample-pkg",
  "version_id":  "2.0.0",
  "cohort_id":   "dep-edges-from-public-manifests",
  "metric":      "observed_dependency_count",
  "value":       "1247",
  "k_effective": null,
  "k_min":       null,
  "window_start":"2026-05-26T00:00:00Z",
  "window_end":  "2026-05-27T00:00:00Z",
  "source_mode": "D",
  "provenance":  "registry-scraper@mirror-eu-1"
}

Leerstellen

Was das Schema nicht kennt

Die folgenden Angaben sind nicht optional, nicht abschaltbar und nicht „nur intern“ — für sie existiert schlicht kein Feld. Ein SDK-Fehler, der so etwas mitschicken wollte, produziert eine abgelehnte Submission, keinen Datensatz.

IP- oder MAC-Adresse Hostname, FQDN Username, E-Mail, OS-User Prozess-ID Dateipfade des Hosts Git-Pfade, Branch-Namen Maintainer-Klarnamen Inhalte von Umgebungsvariablen Zeitstempel feiner als das Fenster

Zwei Gates, nicht eines

Der lokale Aggregator prüft vor dem Verlassen der Maschine, der Mirror prüft beim Eingang noch einmal. Beide Prüfungen sind identisch streng — die zweite existiert für den Fall, dass die erste umgangen wurde.

__small_cohorts

Bleibt eine Kohorte unter k_min, wird sie nicht verworfen und auch nicht knapp aufgefüllt, sondern in einen Sammeltopf umgeschrieben. Der Wert geht in die Summe ein, die Kohorte verschwindet.

Ablehnung ohne Spur

Verletzt eine Submission Signatur, Schema, k-Grenze oder Fenster-Logik, antwortet der Mirror mit einem Fehlercode — und persistiert nichts. Auch nicht zur Fehlersuche.

Fenster-Plausibilität

window_start < window_end, und das Fenster darf nicht in der Zukunft liegen; toleriert werden fünf Minuten Uhren-Drift. Das schließt vorgezogene Einreichungen aus. Eine untere Zeitgrenze kennt die Prüfung bislang nicht — ein altes, korrekt geordnetes Fenster erfüllt sie.

Snapshots

Veröffentlichung als Open Data

Was ein Mirror akzeptiert, wird veröffentlicht — ohne Login, ohne Paywall, ohne eine zweite, reichhaltigere Fassung für zahlende Dritte. Es gibt nur diesen einen Datenbestand.

Eigenschaften der veröffentlichten Snapshots
Eigenschaft Wert
Zeitplan täglich 00:00 UTC
Formate Parquet als primäres Format, dazu CSV.gz — auswertbar ohne Pandas oder sonstiges Tooling
Varianten raw sind die Roh-Aggregate und die kanonische Fassung fürs Audit; resolved fasst Projekt-Aliase zusammen. Beide entstehen täglich; __small_cohorts wird auch in resolved nie namentlich aufgelöst.
Integrität Beide Varianten tragen denselben SHA256-Manifest-Eintrag des zugrundeliegenden Datenstandes plus den Hash der verwendeten Alias-Tabelle — auch die raw-Variante, damit von der Audit-Seite aus belegbar bleibt, wogegen aufgelöst wurde.
Lizenz CC-BY-4.0 als Vorab-Beschluss; solange die Rechtsfreigabe aussteht, trägt jeder Snapshot eine Provisional-Klausel im Manifest
Retention unbegrenzt — ältere Snapshots werden archiviert, nicht gelöscht

Neben den Snapshots steht dieselbe Struktur live über die Aggregat-API und den Ereignis-Stream zur Verfügung, ebenfalls ohne Login. Wer prüfen will, ob zwei Mirrors dasselbe sagen, vergleicht die veröffentlichten Audit-Log-Köpfe.