chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,9 @@
"""_shared — single-source CVDIAG bootstrap shared across all 12 Python
integration backends.
The canonical copy lives at ``showcase/integrations/_shared/``; each Python
integration carries a ``_shared`` symlink → ``../_shared`` that the harness
build tooling (``stageSharedModules()`` / ``stage_shared()``) dereferences into
a real directory inside that integration's Docker build context, landing at
``/app/_shared/`` (``/app`` is on PYTHONPATH for all 12). Plan unit: L0-C.
"""
@@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""codegen_cvdiag_schema.py — generate ``cvdiag_schema.py`` (Pydantic v2 models)
from the canonical JSON Schema ``showcase/harness/src/cvdiag/schema.json``.
The Python data-plane models are CODE-GENERATED (never hand-written) so they
stay byte-for-byte in lockstep with the cross-language schema that L0-A owns.
``schema.json`` is the single intermediate representation consumed here, by the
.NET binding (L0-D), the Java binding (L0-E), and the TS binding (L0-F).
Run from the repo root::
python3 showcase/integrations/_shared/codegen_cvdiag_schema.py # write
python3 showcase/integrations/_shared/codegen_cvdiag_schema.py --check # CI drift check
Plan unit: L0-C.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# ── Path resolution ──────────────────────────────────────────────────────────
# This file lives at showcase/integrations/_shared/codegen_cvdiag_schema.py;
# the schema lives at showcase/harness/src/cvdiag/schema.json. Resolve both
# relative to this file so codegen works from any CWD.
_HERE = Path(__file__).resolve().parent
_REPO_SHOWCASE = _HERE.parent.parent # → showcase/
_SCHEMA_JSON = _REPO_SHOWCASE / "harness" / "src" / "cvdiag" / "schema.json"
_OUTPUT = _HERE / "cvdiag_schema.py"
def _enum_member_name(value: str) -> str:
"""Map a dotted boundary/enum literal to a PascalCase enum member name.
``probe.dom.firsttoken`` → ``PROBE_DOM_FIRSTTOKEN``; ``ok`` → ``OK``.
Uses SCREAMING_SNAKE so the member set is stable + collision-free.
"""
return value.replace(".", "_").replace("-", "_").upper()
def _metadata_class_name(boundary: str) -> str:
"""``probe.dom.firsttoken`` → ``MetadataProbeDomFirsttoken``."""
parts = boundary.replace("-", "_").split(".")
camel = "".join(p[:1].upper() + p[1:] for p in parts)
return f"Metadata{camel}"
def build_module(schema: dict) -> str:
"""Render the full ``cvdiag_schema.py`` source from the schema IR."""
defs = schema["$defs"]
layers: list[str] = defs["layers"]
outcomes: list[str] = defs["outcomes"]
boundaries: list[str] = defs["boundaries"]
edge_keys: list[str] = defs["edge_header_keys"]
boundary_meta: dict[str, list[str]] = defs["boundary_metadata_keys"]
test_id_pattern: str = schema["properties"]["test_id"]["pattern"]
span_id_pattern: str = schema["properties"]["span_id"]["pattern"]
slug_pattern: str = schema["properties"]["slug"]["pattern"]
schema_version: int = schema["schema_version"]
out: list[str] = []
a = out.append
a('"""cvdiag_schema.py — GENERATED Pydantic v2 models for the CVDIAG envelope.')
a("")
a("DO NOT EDIT BY HAND. This file is code-generated from")
a("``showcase/harness/src/cvdiag/schema.json`` by")
a("``showcase/integrations/_shared/codegen_cvdiag_schema.py``. Re-run that")
a("script (and commit the result) whenever the schema changes; CI runs the")
a("generator with ``--check`` to fail on drift. Plan unit: L0-C.")
a('"""')
a("")
a("from __future__ import annotations")
a("")
a("from enum import Enum")
a("from typing import Any, Optional")
a("")
a("from pydantic import BaseModel, ConfigDict, Field, model_validator")
a("")
a(f"SCHEMA_VERSION = {schema_version}")
a("")
a("# UUIDv7 (RFC 9562) pattern for ``test_id`` — version nibble 7, variant 10.")
a(f"TEST_ID_PATTERN = r{test_id_pattern!r}")
a(f"SPAN_ID_PATTERN = r{span_id_pattern!r}")
a(f"SLUG_PATTERN = r{slug_pattern!r}")
a("")
a("")
# ── Enums ────────────────────────────────────────────────────────────────
a("class CvdiagLayer(str, Enum):")
a(' """Owning layer of a CVDIAG envelope (spec §5)."""')
a("")
for lyr in layers:
a(f' {_enum_member_name(lyr)} = "{lyr}"')
a("")
a("")
a("class CvdiagOutcome(str, Enum):")
a(' """Terminal outcome of a boundary observation (spec §5)."""')
a("")
for oc in outcomes:
a(f' {_enum_member_name(oc)} = "{oc}"')
a("")
a("")
a("class CvdiagBoundary(str, Enum):")
a(' """The closed set of 29 data-plane + 4 accounting boundaries (spec §5)."""')
a("")
for b in boundaries:
a(f' {_enum_member_name(b)} = "{b}"')
a("")
a("")
# ── EdgeHeaders ────────────────────────────────────────────────────────────
a("class EdgeHeaders(BaseModel):")
a(' """The closed 9-key edge-header bag (spec §5). Absent → ``None``.')
a("")
a(" ``model_config`` forbids extra keys so a forbidden/unknown edge header")
a(" can never round-trip through this model.")
a(' """')
a("")
a(' model_config = ConfigDict(extra="forbid")')
a("")
for key in edge_keys:
# Header names contain hyphens → use an alias and a sanitized field name.
field = key.replace("-", "_")
a(f' {field}: Optional[str] = Field(default=None, alias="{key}")')
a("")
a("")
# ── Per-boundary metadata models (data-plane boundaries only) ──────────────
a("# ── Per-boundary metadata models (one per data-plane boundary, spec §5) ──")
a("# Each forbids extra keys so an unknown metadata key is surfaced (caller")
a("# stamps ``_metadata_dropped`` on the envelope).")
a("")
metadata_class_map: list[tuple[str, str]] = []
for boundary in boundaries:
keys = boundary_meta.get(boundary)
if keys is None:
# Accounting (cvdiag.*) boundaries carry a free-form metadata bag.
continue
cls = _metadata_class_name(boundary)
metadata_class_map.append((boundary, cls))
a("")
a(f"class {cls}(BaseModel):")
a(f' """Metadata for boundary ``{boundary}`` (closed key set)."""')
a("")
a(' model_config = ConfigDict(extra="forbid")')
a("")
for k in keys:
a(f" {k}: Optional[Any] = None")
a("")
a("")
# Map from boundary literal → metadata model class (for callers/tests).
a("#: boundary literal → its closed metadata model (data-plane only).")
a("BOUNDARY_METADATA_MODEL: dict[str, type[BaseModel]] = {")
for boundary, cls in metadata_class_map:
a(f' "{boundary}": {cls},')
a("}")
a("")
a("")
# ── Envelope ───────────────────────────────────────────────────────────────
a("class CvdiagEnvelope(BaseModel):")
a(' """The CVDIAG flap-observability envelope (spec §5).')
a("")
a(" Unknown TOP-LEVEL keys are dropped (closed-world) and the drop is")
a(" recorded via ``_metadata_dropped``; the ``metadata`` bag itself is")
a(" free-form here (per-boundary closed validation is applied separately")
a(" via ``BOUNDARY_METADATA_MODEL`` so a metadata-only unknown key does not")
a(" reject the whole envelope, it just stamps ``_metadata_dropped``).")
a(' """')
a("")
a(' model_config = ConfigDict(populate_by_name=True, extra="ignore")')
a("")
a(f" schema_version: int = SCHEMA_VERSION")
a(" test_id: str = Field(pattern=TEST_ID_PATTERN)")
a(" trace_id: str")
a(" span_id: str = Field(pattern=SPAN_ID_PATTERN)")
a(" parent_span_id: Optional[str] = None")
a(" layer: CvdiagLayer")
a(" boundary: CvdiagBoundary")
a(" slug: str = Field(pattern=SLUG_PATTERN)")
a(" demo: str")
a(" ts: str")
a(" mono_ns: int")
a(" duration_ms: Optional[int] = None")
a(" outcome: CvdiagOutcome")
a(" edge_headers: EdgeHeaders")
a(" metadata: dict[str, Any] = Field(default_factory=dict)")
a(' metadata_dropped: bool = Field(default=False, alias="_metadata_dropped")')
a(' truncated: bool = Field(default=False, alias="_truncated")')
a("")
a(' @model_validator(mode="before")')
a(" @classmethod")
a(" def _stamp_dropped_unknown_keys(cls, data: Any) -> Any:")
a(' """Stamp ``_metadata_dropped`` when unknown top-level OR metadata keys')
a(" are present, then strip the unknown top-level keys (closed-world).")
a(' """')
a(" if not isinstance(data, dict):")
a(" return data")
a(" known = set(cls.model_fields.keys())")
a(" aliases = {")
a(" f.alias for f in cls.model_fields.values() if f.alias is not None")
a(" }")
a(" allowed = known | aliases")
a(" dropped = False")
a(" cleaned: dict[str, Any] = {}")
a(" for key, value in data.items():")
a(" if key in allowed:")
a(" cleaned[key] = value")
a(" else:")
a(" dropped = True # unknown top-level key → drop + stamp")
a(" # Unknown metadata keys (against the per-boundary closed model).")
a(' boundary = cleaned.get("boundary")')
a(' meta = cleaned.get("metadata")')
a(" if isinstance(boundary, str) and isinstance(meta, dict):")
a(" model = BOUNDARY_METADATA_MODEL.get(boundary)")
a(" if model is not None:")
a(" allowed_meta = set(model.model_fields.keys())")
a(" if any(mk not in allowed_meta for mk in meta):")
a(" dropped = True")
a(" if dropped:")
a(' cleaned["_metadata_dropped"] = True')
a(" return cleaned")
a("")
return "\n".join(out) + "\n"
def load_schema() -> dict:
return json.loads(_SCHEMA_JSON.read_text(encoding="utf-8"))
def render() -> str:
return build_module(load_schema())
def main(argv: list[str]) -> int:
rendered = render()
if "--check" in argv:
try:
on_disk = _OUTPUT.read_text(encoding="utf-8")
except FileNotFoundError:
print(
"CVDIAG codegen: cvdiag_schema.py is MISSING — run the generator and commit.",
file=sys.stderr,
)
return 1
if on_disk == rendered:
print("CVDIAG codegen: cvdiag_schema.py is in sync with schema.json")
return 0
print(
"CVDIAG codegen: cvdiag_schema.py is STALE — run the generator and commit.",
file=sys.stderr,
)
return 1
_OUTPUT.write_text(rendered, encoding="utf-8")
print(f"CVDIAG codegen: wrote {_OUTPUT}")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,273 @@
"""cvdiag_bootstrap.py — single-source CVDIAG runtime bootstrap for every Python
integration backend.
Importing this module (``import _shared.cvdiag_bootstrap``) at the top of an
integration entrypoint does three things, once, at import time:
1. **Captures the ``agents.*`` loggers** by attaching a SCOPED stream handler
to the ``agents`` logger so the ``agents._header_forwarding`` (and sibling
``agents.*``) loggers actually EMIT. This fixes the silent-drop bug: those
loggers call ``logger.info(...)`` but, with no handler attached anywhere up
the hierarchy, the records were being discarded. We attach a dedicated
handler to the ``agents`` logger (NOT ``basicConfig(force=True)`` on root)
so the CVDIAG lines reach stdout where the harness greps for them WITHOUT
tearing down the HOST application's own root-logger configuration — the
module is fully inert (no global logging mutation) when cvdiag is disabled,
matching the canary-safe contract the TS emitter upholds.
2. **Resolves the verbosity tier** (default | verbose | debug) and applies the
§6 fail-closed guard: ``CVDIAG_DEBUG`` is REFUSED (raises at import time)
when the deployment environment resolves to ``production`` or cannot be
resolved at all (unknown env is treated as production).
3. **Exposes ``emit_cvdiag(envelope)``** — validates the envelope against the
generated Pydantic model, writes a single ``CVDIAG`` JSON line to stdout,
and best-effort hands the row to the threaded PocketBase writer.
Pure instrumentation: ``emit_cvdiag`` never throws into the caller. The ONE
permitted raise is the fail-closed DEBUG guard during ``setup()`` (a startup
assertion, mirroring the TS emitter's constructor guard).
Plan unit: L0-C.
"""
from __future__ import annotations
import logging
import os
import sys
from typing import Any, Optional, Union
from _shared.cvdiag_pb_writer import CvdiagPbWriter
from _shared.cvdiag_schema import CvdiagEnvelope
logger = logging.getLogger("agents._cvdiag_bootstrap")
# ── Tier resolution ──────────────────────────────────────────────────────────
# Production-detection env precedence (spec §6):
# SHOWCASE_ENV → RAILWAY_ENVIRONMENT_NAME → PYTHON_ENV.
_ENV_PRECEDENCE = ("SHOWCASE_ENV", "RAILWAY_ENVIRONMENT_NAME", "PYTHON_ENV")
# Module-level singletons, populated by setup().
_TIER: str = "default"
_PB_WRITER: Optional[CvdiagPbWriter] = None
# Idempotency guard: a successful (or degraded) setup() flips this so any
# repeated invocation is a no-op — repeated calls must NOT orphan a second
# flush daemon / PB writer queue.
_SETUP_DONE = False
# True iff cvdiag instrumentation is active. Flipped OFF (fail-closed) when a
# misconfiguration is detected so the backend keeps running with instrumentation
# disabled rather than crashing at import.
_ENABLED = False
_LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s"
# The scoped handler we attach to the ``agents`` logger when ENABLED. Tracked so
# the capture install is idempotent and ``reset_for_test`` can detach it,
# leaving no residual host-logging mutation between tests.
_AGENTS_LOG_NAME = "agents"
_CAPTURE_HANDLER: Optional[logging.Handler] = None
def _install_agents_log_capture() -> None:
"""Attach a scoped stream handler to the ``agents`` logger (idempotent).
This is the silent-drop fix WITHOUT the global blast radius of
``basicConfig(force=True)``: we never touch the root logger's handlers, so
the host application's own logging configuration is preserved. The handler
is attached only when cvdiag is ENABLED; a disabled / degraded backend
leaves host logging byte-for-byte untouched.
"""
global _CAPTURE_HANDLER
if _CAPTURE_HANDLER is not None:
return
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(_LOG_FORMAT))
agents_logger = logging.getLogger(_AGENTS_LOG_NAME)
agents_logger.addHandler(handler)
# Ensure ``agents.*`` records at INFO survive the level filter even if the
# host left the (effective) level above INFO; scoped to the agents subtree.
if agents_logger.level == logging.NOTSET or agents_logger.level > logging.INFO:
agents_logger.setLevel(logging.INFO)
_CAPTURE_HANDLER = handler
def resolve_env_label(env: Optional[dict[str, str]] = None) -> Optional[str]:
"""Resolve the deployment-environment label (lowercased) or ``None``.
Precedence: ``SHOWCASE_ENV`` → ``RAILWAY_ENVIRONMENT_NAME`` → ``PYTHON_ENV``.
"""
src = env if env is not None else os.environ
for key in _ENV_PRECEDENCE:
raw = src.get(key)
if raw is not None and raw != "":
return str(raw).lower()
return None
def _resolve_tier(env: dict[str, str]) -> str:
"""Resolve the verbosity tier, applying the §6 fail-closed DEBUG guard.
Raises ``RuntimeError`` (fail-closed) when DEBUG is requested but the
deployment environment is ``production`` or unresolved.
"""
wants_debug = env.get("CVDIAG_DEBUG") == "1"
wants_verbose = env.get("CVDIAG_VERBOSE") == "1"
if wants_debug:
label = resolve_env_label(env)
if label is None:
raise RuntimeError(
"CVDIAG_DEBUG refused: deployment environment is unresolved "
"(SHOWCASE_ENV → RAILWAY_ENVIRONMENT_NAME → PYTHON_ENV all "
"unset); fail-closed treats unknown env as production."
)
if label == "production":
raise RuntimeError(
"CVDIAG_DEBUG refused: deployment environment is production."
)
return "debug"
if wants_verbose:
return "verbose"
return "default"
def setup(env: Optional[dict[str, str]] = None) -> None:
"""Idempotent bootstrap: resolve tier, build the PB writer, capture agents logs.
Runs once at import time. Three safety contracts:
* **Idempotent** — a second invocation after a completed setup() is a
no-op (the ``_SETUP_DONE`` guard); repeated calls must never orphan a
second flush daemon / PB writer queue.
* **Inert when disabled** — a disabled / degraded setup() performs NO
logging mutation: the scoped ``agents`` capture handler is installed
only on the ENABLED path, and the root logger is never touched. Merely
importing this module when cvdiag is off leaves the host application's
logging configuration byte-for-byte intact (canary-safe).
* **Degrade-not-crash** — a misconfiguration (e.g. the §6 fail-closed
DEBUG guard) DISABLES cvdiag instrumentation and logs a warning; it
must NEVER propagate and abort the host backend's module import. The
fail-closed *intent* is preserved (instrumentation stays OFF on a
forbidden DEBUG request) but the backend keeps running. This mirrors
the TS emitter: it throws at construction, but the wrapper catches it
so the host app survives.
"""
global _TIER, _PB_WRITER, _SETUP_DONE, _ENABLED
# (0) Idempotency guard — repeated setup() is a no-op (FIX-3).
if _SETUP_DONE:
return
src = env if env is not None else dict(os.environ)
# (1) Resolve tier. ``_resolve_tier`` raises (fail-closed) on a forbidden
# DEBUG request — catch it here so a misconfig DEGRADES (instrumentation
# OFF) rather than crashing the backend import (FIX-2).
try:
_TIER = _resolve_tier(src)
except RuntimeError as err:
_TIER = "default"
_ENABLED = False
_PB_WRITER = None
_SETUP_DONE = True
logger.warning(
"CVDIAG bootstrap degraded component=_shared reason=%s "
"(instrumentation disabled; backend continues)",
err,
)
return
# (2) Build the threaded PB writer (no-op when CVDIAG_PB_URL unset).
_PB_WRITER = CvdiagPbWriter(
pb_url=src.get("CVDIAG_PB_URL"),
writer_key=src.get("CVDIAG_WRITER_KEY"),
)
_ENABLED = True
_SETUP_DONE = True
# (3) Only NOW — once instrumentation is confirmed ENABLED — install the
# scoped ``agents`` logger capture. A disabled / degraded setup (the early
# returns above) reaches neither this nor any other logging mutation, so
# importing the bootstrap is fully inert when cvdiag is disabled — it never
# touches the host application's root-logger handlers.
_install_agents_log_capture()
logger.info(
"CVDIAG bootstrap component=_shared tier=%s pb_enabled=%s",
_TIER,
str(_PB_WRITER.enabled).lower(),
)
def current_tier() -> str:
"""Return the resolved tier (``default`` | ``verbose`` | ``debug``)."""
return _TIER
def is_enabled() -> bool:
"""True iff cvdiag instrumentation is active (False after a degraded setup)."""
return _ENABLED
def reset_for_test() -> None:
"""Reset module state so a test can re-run ``setup()`` from scratch.
Test-only helper: clears the idempotency guard and singletons. The flush
daemon is a short-lived best-effort daemon thread, so we simply drop the
reference (the thread exits with the process); we do not join it.
Also detaches the scoped ``agents`` capture handler so each test starts from
an unmutated logging tree (otherwise an enabled setup() would leave a
handler attached across tests).
"""
global _TIER, _PB_WRITER, _SETUP_DONE, _ENABLED, _CAPTURE_HANDLER
_TIER = "default"
_PB_WRITER = None
_SETUP_DONE = False
_ENABLED = False
if _CAPTURE_HANDLER is not None:
logging.getLogger(_AGENTS_LOG_NAME).removeHandler(_CAPTURE_HANDLER)
_CAPTURE_HANDLER = None
def emit_cvdiag(envelope: Union[CvdiagEnvelope, dict[str, Any]]) -> None:
"""Emit one CVDIAG envelope: validate → JSON line to stdout → best-effort PB.
Pure instrumentation — catches every error and degrades to a single
``CVDIAG emit-failed`` log line; never raises into the caller.
The shared emit gate is the single chokepoint every integration's backend
emitter routes through. It honors the ``_ENABLED`` flag (``is_enabled()``)
so a DEGRADED setup() (the §6 fail-closed DEBUG misconfig) actually
SUPPRESSES emission — the degrade must win over a live
``CVDIAG_BACKEND_EMITTER=1`` toggle, otherwise the fail-closed intent is
silently defeated and a degraded backend keeps writing envelopes.
"""
# Degrade gate: a disabled (degraded) backend emits nothing, regardless of
# the per-integration CVDIAG_BACKEND_EMITTER toggle.
if not is_enabled():
return
try:
model = (
envelope
if isinstance(envelope, CvdiagEnvelope)
else CvdiagEnvelope.model_validate(envelope)
)
payload = model.model_dump(by_alias=True, exclude_none=False)
# One JSON line to stdout, ``CVDIAG`` tagged so the harness greps it.
sys.stdout.write("CVDIAG " + _dump_json(payload) + "\n")
sys.stdout.flush()
if _PB_WRITER is not None:
_PB_WRITER.enqueue(payload)
except Exception as err: # noqa: BLE001 - instrumentation must not throw
logger.warning("CVDIAG emit-failed error=%s", err)
def _dump_json(payload: dict[str, Any]) -> str:
import json
return json.dumps(payload, separators=(",", ":"), default=str)
# Run the bootstrap at import time (the whole point — importing this module
# wires logging + tier + PB writer for the integration entrypoint).
setup()
@@ -0,0 +1,251 @@
"""cvdiag_pb_writer.py — best-effort, background (threaded) PocketBase flush for
CVDIAG envelopes emitted from the Python integration backends.
Contract (spec §7 — pure instrumentation, never blocks the observed boundary):
- ``enqueue(envelope)`` returns immediately; it only appends to an in-memory
queue. A single daemon worker thread drains the queue on a ≤1s window and
POSTs to the PocketBase ``cvdiag_events`` collection (CREATE-only).
- A PB write failure is swallowed and logged once as
``CVDIAG pb-write-failed`` — it must NEVER propagate into the caller.
- When ``CVDIAG_PB_URL`` is unset the writer is a no-op sink (enqueue still
returns immediately; nothing is flushed). This keeps local/unit runs free
of network side effects.
Authentication (see the 1779990200_create_cvdiag_events.js migration):
The ``cvdiag_events`` createRule requires the caller to authenticate as a
``cvdiag_api_keys`` auth record whose ``role`` is ``"writer"`` —
@request.auth.collectionName = "cvdiag_api_keys" && @request.auth.role = "writer"
PocketBase has NO notion of a bespoke header, so a header-only request is
UNAUTHENTICATED and the CREATE 4xxs (the createRule evaluates false). The
writer therefore POSTs ``/api/collections/cvdiag_api_keys/auth-with-password``
with the fixed writer identity (``cvdiag-writer@keys.local`` — overridable via
``CVDIAG_WRITER_IDENTITY``) and ``CVDIAG_WRITER_KEY`` as the PASSWORD, caches
the returned token, and sends ``Authorization: Bearer <token>`` on the CREATE.
A 401 (token expiry / bad creds) clears the cached token and triggers a single
re-auth + retry. Auth failure stays best-effort: it degrades to a no-op + the
one-shot ``CVDIAG pb-write-failed`` warn — it NEVER crashes the daemon.
Plan unit: L0-C.
"""
from __future__ import annotations
import json
import logging
import os
import queue
import threading
import urllib.error
import urllib.request
from typing import Any, Optional
logger = logging.getLogger("agents._cvdiag_pb_writer")
# Flush window: drain at least this often (spec §7 R5-F12: ≤1s window).
FLUSH_WINDOW_S = 1.0
# Bounded queue — drop-oldest on overflow so a stuck flush can't grow unbounded.
QUEUE_CAP = 5000
# Per-flush HTTP timeout so a hung PB never wedges the worker thread.
HTTP_TIMEOUT_S = 5.0
# Auth collection + fixed default identity of the seeded writer record. The
# migration seeds email ``cvdiag-writer@keys.local`` with role ``writer``;
# CVDIAG_WRITER_KEY is that record's PASSWORD. The identity is overridable for
# environments that rotate the writer email, but defaults to the seeded value.
WRITER_AUTH_COLLECTION = "cvdiag_api_keys"
DEFAULT_WRITER_IDENTITY = "cvdiag-writer@keys.local"
class CvdiagPbWriter:
"""Threaded, best-effort PocketBase writer. Construct once at import time.
The worker thread is a daemon so it never keeps the process alive on exit.
"""
def __init__(
self,
pb_url: Optional[str] = None,
writer_key: Optional[str] = None,
*,
writer_identity: Optional[str] = None,
flush_window_s: float = FLUSH_WINDOW_S,
) -> None:
self._pb_url = pb_url if pb_url is not None else os.environ.get("CVDIAG_PB_URL")
self._writer_key = (
writer_key
if writer_key is not None
else os.environ.get("CVDIAG_WRITER_KEY")
)
self._writer_identity = (
writer_identity
if writer_identity is not None
else os.environ.get("CVDIAG_WRITER_IDENTITY", DEFAULT_WRITER_IDENTITY)
)
self._flush_window_s = flush_window_s
self._queue: "queue.Queue[dict[str, Any]]" = queue.Queue(maxsize=QUEUE_CAP)
self._logged_failure = False
self._started = False
self._lock = threading.Lock()
self._worker: Optional[threading.Thread] = None
# Cached auth-with-password token. Only the single daemon worker thread
# touches this (auth + CREATE both run inside ``_run``), so no lock is
# needed. ``None`` means "not authenticated yet / cleared after a 401".
self._auth_token: Optional[str] = None
@property
def enabled(self) -> bool:
"""True iff a PB target URL is configured (otherwise this is a no-op)."""
return bool(self._pb_url)
def _ensure_worker(self) -> None:
if self._started:
return
with self._lock:
if self._started:
return
self._worker = threading.Thread(
target=self._run,
name="cvdiag-pb-writer",
daemon=True,
)
self._worker.start()
self._started = True
def enqueue(self, envelope: dict[str, Any]) -> None:
"""Queue one envelope for background flush. Never blocks; never raises.
On a full queue we drop the OLDEST entry (instrumentation must shed
load rather than block the boundary it observes).
"""
if not self.enabled:
return
try:
self._ensure_worker()
try:
self._queue.put_nowait(envelope)
except queue.Full:
# Drop-oldest, then retry once. Best-effort; never block.
try:
self._queue.get_nowait()
except queue.Empty:
pass
try:
self._queue.put_nowait(envelope)
except queue.Full:
pass
except Exception as err: # pragma: no cover - defensive belt
self._log_failure(err)
def _run(self) -> None:
while True:
try:
envelope = self._queue.get(timeout=self._flush_window_s)
except queue.Empty:
continue
batch = [envelope]
# Coalesce anything else already queued into this flush.
while True:
try:
batch.append(self._queue.get_nowait())
except queue.Empty:
break
for env in batch:
# Never-propagate: isolate each record so no single envelope
# can unwind ``_run`` and PERMANENTLY kill the flush daemon.
try:
self._post(env)
except Exception as err: # noqa: BLE001 - daemon must survive
self._log_failure(err)
def _authenticate(self) -> Optional[str]:
"""Auth-with-password as the writer role; return + cache the token.
Returns the cached token if present, else POSTs the writer identity +
``CVDIAG_WRITER_KEY`` (the writer record PASSWORD) to the
``cvdiag_api_keys`` auth-with-password endpoint and caches the token.
Returns ``None`` on any failure (bad creds, unreachable PB, malformed
response) — the caller degrades to a no-op. NEVER raises.
"""
if self._auth_token:
return self._auth_token
url = self._pb_url
if not url or not self._writer_key:
return None
endpoint = (
url.rstrip("/")
+ f"/api/collections/{WRITER_AUTH_COLLECTION}/auth-with-password"
)
body = json.dumps(
{"identity": self._writer_identity, "password": self._writer_key}
).encode("utf-8")
req = urllib.request.Request(
endpoint,
data=body,
method="POST",
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_S) as resp:
payload = json.loads(resp.read().decode("utf-8"))
token = payload.get("token")
if not token:
return None
self._auth_token = token
return token
def _post(self, envelope: dict[str, Any]) -> None:
url = self._pb_url
if not url:
return
endpoint = url.rstrip("/") + "/api/collections/cvdiag_events/records"
# Never-propagate: a single bad record (e.g. a non-JSON-serializable
# envelope that makes ``json.dumps`` raise ``TypeError``) or an auth
# failure must be logged/dropped, NOT allowed to escape and kill the
# drain daemon. This mirrors the TS pb-writer ``writeBatch`` contract —
# one bad row / a failed auth degrades to a warn; the worker survives.
try:
body = json.dumps(envelope).encode("utf-8")
# Authenticate as the writer-role record (createRule requires it).
# On a 401 (token expiry / stale token) clear the cache and re-auth
# once before giving up — but never loop.
self._create_with_auth(endpoint, body, allow_reauth=True)
except Exception as err: # noqa: BLE001 - instrumentation must never throw
self._log_failure(err)
def _create_with_auth(
self, endpoint: str, body: bytes, *, allow_reauth: bool
) -> None:
"""POST the CREATE with a Bearer token; re-auth once on a 401."""
token = self._authenticate()
if not token:
# Auth failed (bad/missing writer key, unreachable PB). Degrade to a
# no-op + the one-shot warn — never crash the daemon.
self._log_failure(RuntimeError("CVDIAG writer auth failed"))
return
req = urllib.request.Request(
endpoint,
data=body,
method="POST",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
},
)
try:
urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_S).close()
except urllib.error.HTTPError as err:
# 401 → token expired / revoked. Clear the cache and re-auth ONCE.
if err.code == 401 and allow_reauth:
self._auth_token = None
self._create_with_auth(endpoint, body, allow_reauth=False)
return
raise
def _log_failure(self, err: Exception) -> None:
# Log the first failure at WARNING; subsequent ones at DEBUG to avoid
# spamming the log on a sustained PB outage.
if not self._logged_failure:
self._logged_failure = True
logger.warning("CVDIAG pb-write-failed error=%s", err)
else:
logger.debug("CVDIAG pb-write-failed error=%s", err)
@@ -0,0 +1,482 @@
"""cvdiag_schema.py — GENERATED Pydantic v2 models for the CVDIAG envelope.
DO NOT EDIT BY HAND. This file is code-generated from
``showcase/harness/src/cvdiag/schema.json`` by
``showcase/integrations/_shared/codegen_cvdiag_schema.py``. Re-run that
script (and commit the result) whenever the schema changes; CI runs the
generator with ``--check`` to fail on drift. Plan unit: L0-C.
"""
from __future__ import annotations
from enum import Enum
from typing import Any, Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
SCHEMA_VERSION = 1
# UUIDv7 (RFC 9562) pattern for ``test_id`` — version nibble 7, variant 10.
TEST_ID_PATTERN = (
r"^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
)
SPAN_ID_PATTERN = r"^[0-9a-f]{16}$"
SLUG_PATTERN = r"^[a-z][a-z0-9-]{0,63}$"
class CvdiagLayer(str, Enum):
"""Owning layer of a CVDIAG envelope (spec §5)."""
PROBE = "probe"
BACKEND = "backend"
AIMOCK = "aimock"
class CvdiagOutcome(str, Enum):
"""Terminal outcome of a boundary observation (spec §5)."""
OK = "ok"
ERR = "err"
TIMEOUT = "timeout"
INFO = "info"
class CvdiagBoundary(str, Enum):
"""The closed set of 29 data-plane + 4 accounting boundaries (spec §5)."""
PROBE_START = "probe.start"
PROBE_NAVIGATE_COMPLETE = "probe.navigate.complete"
PROBE_MESSAGE_SEND = "probe.message.send"
PROBE_DOM_CONTAINER_MOUNT = "probe.dom.container.mount"
PROBE_DOM_FIRSTTOKEN = "probe.dom.firsttoken"
PROBE_DOM_ALTERNATE_CONTENT = "probe.dom.alternate_content"
PROBE_SSE_EVENT = "probe.sse.event"
PROBE_SSE_ABORTED = "probe.sse.aborted"
PROBE_NETWORK_ERROR = "probe.network.error"
PROBE_NETWORK_RESPONSE = "probe.network.response"
PROBE_CONSOLE_ERROR = "probe.console.error"
PROBE_EXIT = "probe.exit"
BACKEND_REQUEST_INGRESS = "backend.request.ingress"
BACKEND_AGENT_ENTER = "backend.agent.enter"
BACKEND_LLM_CALL_START = "backend.llm.call.start"
BACKEND_LLM_CALL_HEARTBEAT = "backend.llm.call.heartbeat"
BACKEND_LLM_CALL_RESPONSE = "backend.llm.call.response"
BACKEND_SSE_FIRST_BYTE = "backend.sse.first_byte"
BACKEND_SSE_EVENT = "backend.sse.event"
BACKEND_SSE_ABORTED = "backend.sse.aborted"
BACKEND_AGENT_EXIT = "backend.agent.exit"
BACKEND_RESPONSE_COMPLETE = "backend.response.complete"
BACKEND_ERROR_CAUGHT = "backend.error.caught"
AIMOCK_REQUEST_INGRESS = "aimock.request.ingress"
AIMOCK_MATCH_DECISION = "aimock.match.decision"
AIMOCK_RESPONSE_START = "aimock.response.start"
AIMOCK_SSE_CHUNK = "aimock.sse.chunk"
AIMOCK_RESPONSE_ABORTED = "aimock.response.aborted"
AIMOCK_RESPONSE_COMPLETE = "aimock.response.complete"
CVDIAG_PURGE_AUDIT = "cvdiag.purge_audit"
CVDIAG_COLLISION_DETECTED = "cvdiag.collision_detected"
CVDIAG_QUEUE_DROPPED = "cvdiag.queue_dropped"
CVDIAG_METADATA_DROPPED = "cvdiag.metadata_dropped"
class EdgeHeaders(BaseModel):
"""The closed 9-key edge-header bag (spec §5). Absent → ``None``.
``model_config`` forbids extra keys so a forbidden/unknown edge header
can never round-trip through this model.
"""
model_config = ConfigDict(extra="forbid")
cf_ray: Optional[str] = Field(default=None, alias="cf-ray")
cf_mitigated: Optional[str] = Field(default=None, alias="cf-mitigated")
cf_cache_status: Optional[str] = Field(default=None, alias="cf-cache-status")
x_railway_edge: Optional[str] = Field(default=None, alias="x-railway-edge")
x_railway_request_id: Optional[str] = Field(
default=None, alias="x-railway-request-id"
)
x_hikari_trace: Optional[str] = Field(default=None, alias="x-hikari-trace")
retry_after: Optional[str] = Field(default=None, alias="retry-after")
via: Optional[str] = Field(default=None, alias="via")
server: Optional[str] = Field(default=None, alias="server")
# ── Per-boundary metadata models (one per data-plane boundary, spec §5) ──
# Each forbids extra keys so an unknown metadata key is surfaced (caller
# stamps ``_metadata_dropped`` on the envelope).
class MetadataProbeStart(BaseModel):
"""Metadata for boundary ``probe.start`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
url: Optional[Any] = None
viewport: Optional[Any] = None
class MetadataProbeNavigateComplete(BaseModel):
"""Metadata for boundary ``probe.navigate.complete`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
url: Optional[Any] = None
nav_ms: Optional[Any] = None
http_status: Optional[Any] = None
class MetadataProbeMessageSend(BaseModel):
"""Metadata for boundary ``probe.message.send`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
message_index: Optional[Any] = None
char_count: Optional[Any] = None
demo: Optional[Any] = None
class MetadataProbeDomContainerMount(BaseModel):
"""Metadata for boundary ``probe.dom.container.mount`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
delta_ms_from_start: Optional[Any] = None
class MetadataProbeDomFirsttoken(BaseModel):
"""Metadata for boundary ``probe.dom.firsttoken`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
delta_ms_from_start: Optional[Any] = None
text_length: Optional[Any] = None
class MetadataProbeDomAlternate_content(BaseModel):
"""Metadata for boundary ``probe.dom.alternate_content`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
child_type_histogram: Optional[Any] = None
class MetadataProbeSseEvent(BaseModel):
"""Metadata for boundary ``probe.sse.event`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
event_type: Optional[Any] = None
payload_size_bytes: Optional[Any] = None
sequence_num: Optional[Any] = None
class MetadataProbeSseAborted(BaseModel):
"""Metadata for boundary ``probe.sse.aborted`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
termination_kind: Optional[Any] = None
bytes_before_abort: Optional[Any] = None
class MetadataProbeNetworkError(BaseModel):
"""Metadata for boundary ``probe.network.error`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
url: Optional[Any] = None
error_class: Optional[Any] = None
response_status: Optional[Any] = None
class MetadataProbeNetworkResponse(BaseModel):
"""Metadata for boundary ``probe.network.response`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
url: Optional[Any] = None
status: Optional[Any] = None
content_length: Optional[Any] = None
duration_ms: Optional[Any] = None
class MetadataProbeConsoleError(BaseModel):
"""Metadata for boundary ``probe.console.error`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
level: Optional[Any] = None
message_scrubbed: Optional[Any] = None
source_file: Optional[Any] = None
line_col: Optional[Any] = None
class MetadataProbeExit(BaseModel):
"""Metadata for boundary ``probe.exit`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
terminal_outcome: Optional[Any] = None
total_duration_ms: Optional[Any] = None
sse_event_count: Optional[Any] = None
first_token_delta_ms: Optional[Any] = None
class MetadataBackendRequestIngress(BaseModel):
"""Metadata for boundary ``backend.request.ingress`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
method: Optional[Any] = None
path: Optional[Any] = None
content_length: Optional[Any] = None
class MetadataBackendAgentEnter(BaseModel):
"""Metadata for boundary ``backend.agent.enter`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
agent_name: Optional[Any] = None
model_id: Optional[Any] = None
class MetadataBackendLlmCallStart(BaseModel):
"""Metadata for boundary ``backend.llm.call.start`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
provider: Optional[Any] = None
model: Optional[Any] = None
prompt_token_count_estimate: Optional[Any] = None
class MetadataBackendLlmCallHeartbeat(BaseModel):
"""Metadata for boundary ``backend.llm.call.heartbeat`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
elapsed_ms_since_start: Optional[Any] = None
class MetadataBackendLlmCallResponse(BaseModel):
"""Metadata for boundary ``backend.llm.call.response`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
provider: Optional[Any] = None
model: Optional[Any] = None
response_token_count: Optional[Any] = None
latency_ms: Optional[Any] = None
error_class: Optional[Any] = None
class MetadataBackendSseFirst_byte(BaseModel):
"""Metadata for boundary ``backend.sse.first_byte`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
delta_ms_from_ingress: Optional[Any] = None
class MetadataBackendSseEvent(BaseModel):
"""Metadata for boundary ``backend.sse.event`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
event_type: Optional[Any] = None
payload_size_bytes: Optional[Any] = None
sequence_num: Optional[Any] = None
class MetadataBackendSseAborted(BaseModel):
"""Metadata for boundary ``backend.sse.aborted`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
termination_kind: Optional[Any] = None
bytes_before_abort: Optional[Any] = None
class MetadataBackendAgentExit(BaseModel):
"""Metadata for boundary ``backend.agent.exit`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
terminal_outcome: Optional[Any] = None
total_duration_ms: Optional[Any] = None
class MetadataBackendResponseComplete(BaseModel):
"""Metadata for boundary ``backend.response.complete`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
http_status: Optional[Any] = None
content_length: Optional[Any] = None
total_duration_ms: Optional[Any] = None
sse_event_count: Optional[Any] = None
class MetadataBackendErrorCaught(BaseModel):
"""Metadata for boundary ``backend.error.caught`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
exception_type: Optional[Any] = None
message_scrubbed: Optional[Any] = None
stack_brief: Optional[Any] = None
truncated: Optional[Any] = None
class MetadataAimockRequestIngress(BaseModel):
"""Metadata for boundary ``aimock.request.ingress`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
path: Optional[Any] = None
content_length: Optional[Any] = None
match_keys: Optional[Any] = None
class MetadataAimockMatchDecision(BaseModel):
"""Metadata for boundary ``aimock.match.decision`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
fixture_id: Optional[Any] = None
match_score: Optional[Any] = None
reject_reasons: Optional[Any] = None
class MetadataAimockResponseStart(BaseModel):
"""Metadata for boundary ``aimock.response.start`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
delta_ms_from_ingress: Optional[Any] = None
class MetadataAimockSseChunk(BaseModel):
"""Metadata for boundary ``aimock.sse.chunk`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
chunk_size_bytes: Optional[Any] = None
sequence_num: Optional[Any] = None
class MetadataAimockResponseAborted(BaseModel):
"""Metadata for boundary ``aimock.response.aborted`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
termination_kind: Optional[Any] = None
bytes_before_abort: Optional[Any] = None
class MetadataAimockResponseComplete(BaseModel):
"""Metadata for boundary ``aimock.response.complete`` (closed key set)."""
model_config = ConfigDict(extra="forbid")
http_status: Optional[Any] = None
total_bytes: Optional[Any] = None
total_duration_ms: Optional[Any] = None
chunk_count: Optional[Any] = None
#: boundary literal → its closed metadata model (data-plane only).
BOUNDARY_METADATA_MODEL: dict[str, type[BaseModel]] = {
"probe.start": MetadataProbeStart,
"probe.navigate.complete": MetadataProbeNavigateComplete,
"probe.message.send": MetadataProbeMessageSend,
"probe.dom.container.mount": MetadataProbeDomContainerMount,
"probe.dom.firsttoken": MetadataProbeDomFirsttoken,
"probe.dom.alternate_content": MetadataProbeDomAlternate_content,
"probe.sse.event": MetadataProbeSseEvent,
"probe.sse.aborted": MetadataProbeSseAborted,
"probe.network.error": MetadataProbeNetworkError,
"probe.network.response": MetadataProbeNetworkResponse,
"probe.console.error": MetadataProbeConsoleError,
"probe.exit": MetadataProbeExit,
"backend.request.ingress": MetadataBackendRequestIngress,
"backend.agent.enter": MetadataBackendAgentEnter,
"backend.llm.call.start": MetadataBackendLlmCallStart,
"backend.llm.call.heartbeat": MetadataBackendLlmCallHeartbeat,
"backend.llm.call.response": MetadataBackendLlmCallResponse,
"backend.sse.first_byte": MetadataBackendSseFirst_byte,
"backend.sse.event": MetadataBackendSseEvent,
"backend.sse.aborted": MetadataBackendSseAborted,
"backend.agent.exit": MetadataBackendAgentExit,
"backend.response.complete": MetadataBackendResponseComplete,
"backend.error.caught": MetadataBackendErrorCaught,
"aimock.request.ingress": MetadataAimockRequestIngress,
"aimock.match.decision": MetadataAimockMatchDecision,
"aimock.response.start": MetadataAimockResponseStart,
"aimock.sse.chunk": MetadataAimockSseChunk,
"aimock.response.aborted": MetadataAimockResponseAborted,
"aimock.response.complete": MetadataAimockResponseComplete,
}
class CvdiagEnvelope(BaseModel):
"""The CVDIAG flap-observability envelope (spec §5).
Unknown TOP-LEVEL keys are dropped (closed-world) and the drop is
recorded via ``_metadata_dropped``; the ``metadata`` bag itself is
free-form here (per-boundary closed validation is applied separately
via ``BOUNDARY_METADATA_MODEL`` so a metadata-only unknown key does not
reject the whole envelope, it just stamps ``_metadata_dropped``).
"""
model_config = ConfigDict(populate_by_name=True, extra="ignore")
schema_version: int = SCHEMA_VERSION
test_id: str = Field(pattern=TEST_ID_PATTERN)
trace_id: str
span_id: str = Field(pattern=SPAN_ID_PATTERN)
parent_span_id: Optional[str] = None
layer: CvdiagLayer
boundary: CvdiagBoundary
slug: str = Field(pattern=SLUG_PATTERN)
demo: str
ts: str
mono_ns: int
duration_ms: Optional[int] = None
outcome: CvdiagOutcome
edge_headers: EdgeHeaders
metadata: dict[str, Any] = Field(default_factory=dict)
metadata_dropped: bool = Field(default=False, alias="_metadata_dropped")
truncated: bool = Field(default=False, alias="_truncated")
@model_validator(mode="before")
@classmethod
def _stamp_dropped_unknown_keys(cls, data: Any) -> Any:
"""Stamp ``_metadata_dropped`` when unknown top-level OR metadata keys
are present, then strip the unknown top-level keys (closed-world).
"""
if not isinstance(data, dict):
return data
known = set(cls.model_fields.keys())
aliases = {f.alias for f in cls.model_fields.values() if f.alias is not None}
allowed = known | aliases
dropped = False
cleaned: dict[str, Any] = {}
for key, value in data.items():
if key in allowed:
cleaned[key] = value
else:
dropped = True # unknown top-level key → drop + stamp
# Unknown metadata keys (against the per-boundary closed model).
boundary = cleaned.get("boundary")
meta = cleaned.get("metadata")
if isinstance(boundary, str) and isinstance(meta, dict):
model = BOUNDARY_METADATA_MODEL.get(boundary)
if model is not None:
allowed_meta = set(model.model_fields.keys())
if any(mk not in allowed_meta for mk in meta):
dropped = True
if dropped:
cleaned["_metadata_dropped"] = True
return cleaned
@@ -0,0 +1,483 @@
// CvdiagEmitter.cs — the shared .NET CVDIAG emitter (plan unit L0-D; spec §6/§7).
//
// Mirrors the canonical TS `emit.ts` contract for the .NET integrations:
// • Tier resolution from the deployment environment, with the .NET-specific
// precedence SHOWCASE_ENV → RAILWAY_ENVIRONMENT_NAME → ASPNETCORE_ENVIRONMENT
// (the last is the .NET analogue of TS's NODE_ENV).
// • Fail-closed DEBUG: the constructor throws (startup assertion — the ONE
// place the emitter is permitted to throw) when DEBUG is requested in a
// production env, when no env resolves (unknown == production), or when no
// CVDIAG_DEBUG_ALLOW_LIST is provided.
// • §6 tier matrix: a boundary is emitted only if included at the current
// tier; `cvdiag.*` accounting boundaries are always emitted.
// • DEBUG auto-off: after 10 minutes OR 10k events, DEBUG disarms and falls
// back to default-tier inclusion.
// • Closed-world metadata filter: unknown metadata keys for the boundary are
// dropped and `_metadata_dropped` is stamped.
// • Edge-header allow/deny filter (9 allow / 12 deny, exact-match, deny wins,
// case-insensitive, no cf-ip* wildcard).
// • Per-event byte cap by tier; over-budget envelopes are trimmed +
// `_truncated` stamped.
// • EmitEvent → single-line JSON to stdout, PLUS a background fire-and-forget
// PocketBase write (HttpClient, ≤1s timeout) that never blocks or throws
// into the observed boundary.
//
// Pure instrumentation: outside the constructor's startup guard, a CVDIAG
// failure must NEVER throw into the caller (spec §7). All hot-path errors
// degrade to a stderr CVDIAG-tagged warning.
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Diagnostics;
using System.Security.Cryptography;
namespace Copilotkit.Showcase.Cvdiag;
/// <summary>Resolved verbosity tier (cumulative; spec §6).</summary>
public enum CvdiagTier { Default, Verbose, Debug }
/// <summary>Construction options for <see cref="CvdiagEmitter"/>.</summary>
public sealed class CvdiagEmitterOptions
{
/// <summary>Force DEBUG tier (subject to the fail-closed prod guard).</summary>
public bool Debug { get; init; }
/// <summary>Force VERBOSE tier (DEBUG wins if both set).</summary>
public bool Verbose { get; init; }
/// <summary>Environment bag; defaults to the process environment.</summary>
public IReadOnlyDictionary<string, string?>? Env { get; init; }
/// <summary>Owning layer for default envelope fields.</summary>
public CvdiagLayer Layer { get; init; } = CvdiagLayer.Backend;
/// <summary>PocketBase ingest URL; when null, no background write is attempted.</summary>
public string? PbWriteUrl { get; init; }
/// <summary>Injected HttpClient (tests); defaults to a shared 1s-timeout client.</summary>
public HttpClient? HttpClient { get; init; }
/// <summary>Emit the single-line JSON to stdout (default true).</summary>
public bool WriteToStdout { get; init; } = true;
}
/// <summary>Arguments for a single <see cref="CvdiagEmitter.Emit"/> call.</summary>
public sealed class CvdiagEmitArgs
{
public required CvdiagLayer Layer { get; init; }
public required CvdiagBoundary Boundary { get; init; }
public required string Slug { get; init; }
public required string Demo { get; init; }
public required CvdiagOutcome Outcome { get; init; }
public EdgeHeaders? EdgeHeaders { get; init; }
public Dictionary<string, object?>? Metadata { get; init; }
public long? DurationMs { get; init; }
public string? ParentSpanId { get; init; }
/// <summary>Override test_id (e.g. probe.start mints one and threads it).</summary>
public string? TestId { get; init; }
}
public sealed class CvdiagEmitter
{
// §6 hard bounds + §7 byte caps (mirror emit.ts constants).
private const long DebugMaxWallclockMs = 10 * 60 * 1000;
private const int DebugMaxEvents = 10_000;
private static readonly IReadOnlyDictionary<CvdiagTier, int> ByteCapByTier =
new Dictionary<CvdiagTier, int>
{
[CvdiagTier.Default] = 2 * 1024,
[CvdiagTier.Verbose] = 4 * 1024,
[CvdiagTier.Debug] = 16 * 1024,
};
private const string AccountingPrefix = "cvdiag.";
// The 12-name DENY list (spec §5). Exact-match, lowercase, deny wins; the
// cf-ip* family is blocked by these explicit entries, NOT a wildcard.
private static readonly HashSet<string> DenyList = new(StringComparer.Ordinal)
{
"cf-ipcountry", "cf-connecting-ip", "cf-ipcity", "cf-iplatitude",
"cf-iplongitude", "cf-iptimezone", "cf-visitor", "cf-worker",
"true-client-ip", "x-forwarded-for", "x-real-ip", "forwarded",
};
private static readonly HashSet<string> AllowList =
new(CvdiagSchema.EdgeHeaderKeys, StringComparer.Ordinal);
// §6 tier matrix: per data-plane boundary, included-at-tier flags.
private static readonly IReadOnlyDictionary<string, (bool Default, bool Verbose, bool Debug)> TierMatrix =
new Dictionary<string, (bool, bool, bool)>
{
["probe.start"] = (false, true, true),
["probe.navigate.complete"] = (false, true, true),
["probe.message.send"] = (true, true, true),
["probe.dom.container.mount"] = (true, true, true),
["probe.dom.firsttoken"] = (true, true, true),
["probe.dom.alternate_content"] = (true, true, true),
["probe.sse.event"] = (false, true, true),
["probe.sse.aborted"] = (true, true, true),
["probe.network.error"] = (true, true, true),
["probe.network.response"] = (true, true, true),
["probe.console.error"] = (true, true, true),
["probe.exit"] = (true, true, true),
["backend.request.ingress"] = (false, true, true),
["backend.agent.enter"] = (true, true, true),
["backend.llm.call.start"] = (false, true, true),
["backend.llm.call.heartbeat"] = (false, true, true),
["backend.llm.call.response"] = (false, true, true),
["backend.sse.first_byte"] = (false, true, true),
["backend.sse.event"] = (false, false, true),
["backend.sse.aborted"] = (true, true, true),
["backend.agent.exit"] = (true, true, true),
["backend.response.complete"] = (true, true, true),
["backend.error.caught"] = (true, true, true),
["aimock.request.ingress"] = (false, true, true),
["aimock.match.decision"] = (false, true, true),
["aimock.response.start"] = (false, true, true),
["aimock.sse.chunk"] = (false, false, true),
["aimock.response.aborted"] = (true, true, true),
["aimock.response.complete"] = (true, true, true),
};
private static readonly HttpClient SharedHttpClient =
new() { Timeout = TimeSpan.FromSeconds(1) };
private readonly IReadOnlyDictionary<string, string?> _env;
private readonly CvdiagLayer _defaultLayer;
private readonly string? _pbWriteUrl;
private readonly HttpClient _httpClient;
private readonly bool _writeToStdout;
private readonly long _debugDeadlineMs;
private readonly Stopwatch _mono = Stopwatch.StartNew();
private long _debugEventCount;
private bool _debugDisarmed;
public CvdiagTier Tier { get; }
public CvdiagEmitter(CvdiagEmitterOptions? options = null)
{
options ??= new CvdiagEmitterOptions();
_env = options.Env ?? ReadProcessEnv();
_defaultLayer = options.Layer;
_pbWriteUrl = options.PbWriteUrl;
_httpClient = options.HttpClient ?? SharedHttpClient;
_writeToStdout = options.WriteToStdout;
var wantsDebug = options.Debug || _env.GetValueOrDefault("CVDIAG_DEBUG") == "1";
var wantsVerbose = options.Verbose || _env.GetValueOrDefault("CVDIAG_VERBOSE") == "1";
if (wantsDebug)
{
AssertDebugAllowed();
Tier = CvdiagTier.Debug;
_debugDeadlineMs = NowMs() + DebugMaxWallclockMs;
}
else
{
Tier = wantsVerbose ? CvdiagTier.Verbose : CvdiagTier.Default;
}
}
/// <summary>
/// Resolve the deployment-environment label (spec §6 production detection):
/// SHOWCASE_ENV → RAILWAY_ENVIRONMENT_NAME → ASPNETCORE_ENVIRONMENT.
/// Returns the lowercase label, or null if none resolves.
/// </summary>
public static string? ResolveEnvLabel(IReadOnlyDictionary<string, string?> env)
{
var raw = env.GetValueOrDefault("SHOWCASE_ENV")
?? env.GetValueOrDefault("RAILWAY_ENVIRONMENT_NAME")
?? env.GetValueOrDefault("ASPNETCORE_ENVIRONMENT");
return string.IsNullOrEmpty(raw) ? null : raw.ToLowerInvariant();
}
/// <summary>
/// DEBUG startup assertion (spec §6 hard bounds). Throws (fail-closed) when
/// the env is production, unresolved (unknown == production), or no
/// allow-list is provided. The ONE place the emitter may throw.
/// </summary>
private void AssertDebugAllowed()
{
var label = ResolveEnvLabel(_env);
if (label is null)
{
throw new InvalidOperationException(
"CVDIAG_DEBUG refused: deployment environment is unresolved " +
"(SHOWCASE_ENV → RAILWAY_ENVIRONMENT_NAME → ASPNETCORE_ENVIRONMENT all unset); " +
"fail-closed treats unknown env as production.");
}
if (label == "production")
{
throw new InvalidOperationException(
"CVDIAG_DEBUG refused: deployment environment is production.");
}
var allowList = _env.GetValueOrDefault("CVDIAG_DEBUG_ALLOW_LIST");
if (string.IsNullOrWhiteSpace(allowList))
{
throw new InvalidOperationException(
"CVDIAG_DEBUG refused: CVDIAG_DEBUG_ALLOW_LIST is required " +
"(comma-separated slug list) before DEBUG may start.");
}
}
/// <summary>True iff the boundary is included at the current tier.</summary>
public bool ShouldEmit(CvdiagBoundary boundary)
{
var wire = CvdiagSchema.WireValue(boundary);
if (wire.StartsWith(AccountingPrefix, StringComparison.Ordinal))
{
return true; // accounting events always emit
}
if (Tier == CvdiagTier.Debug && IsDebugExpired())
{
return TierMatrix.TryGetValue(wire, out var fb) && fb.Default;
}
if (!TierMatrix.TryGetValue(wire, out var row)) return false;
return Tier switch
{
CvdiagTier.Default => row.Default,
CvdiagTier.Verbose => row.Verbose,
CvdiagTier.Debug => row.Debug,
_ => false,
};
}
private bool IsDebugExpired()
{
if (_debugDisarmed) return true;
if (NowMs() >= _debugDeadlineMs || _debugEventCount >= DebugMaxEvents)
{
_debugDisarmed = true;
return true;
}
return false;
}
/// <summary>
/// Emit one event: tier-filter, mint ids, closed-world metadata filter,
/// byte-cap, then EmitEvent (stdout + background PB write). Returns the
/// built envelope, or null when filtered out / on failure. Never throws.
/// </summary>
public CvdiagEnvelope? Emit(CvdiagEmitArgs args)
{
try
{
if (!ShouldEmit(args.Boundary)) return null;
if (Tier == CvdiagTier.Debug) _debugEventCount++;
var wire = CvdiagSchema.WireValue(args.Boundary);
var isDataPlane = !wire.StartsWith(AccountingPrefix, StringComparison.Ordinal);
var metadata = new Dictionary<string, object?>();
var metadataDropped = false;
if (isDataPlane)
{
(metadata, metadataDropped) = FilterMetadata(wire, args.Metadata);
}
else if (args.Metadata is not null)
{
// Accounting events ride their payload verbatim (trusted internal).
metadata = new Dictionary<string, object?>(args.Metadata);
}
var testId = args.TestId ?? MintTestId();
var envelope = new CvdiagEnvelope
{
SchemaVersion = CvdiagSchema.SchemaVersion,
TestId = testId,
TraceId = testId,
SpanId = MintSpanId(),
ParentSpanId = args.ParentSpanId,
Layer = args.Layer,
Boundary = args.Boundary,
Slug = args.Slug,
Demo = args.Demo,
Ts = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
MonoNs = MonoNs(),
DurationMs = args.DurationMs,
Outcome = args.Outcome,
EdgeHeaders = args.EdgeHeaders ?? new EdgeHeaders(),
Metadata = metadata,
MetadataDropped = metadataDropped,
};
ApplyByteCap(envelope);
EmitEvent(envelope);
return envelope;
}
catch (Exception ex)
{
Console.Error.WriteLine($"CVDIAG emit failed boundary={args.Boundary} error={ex.Message}");
return null;
}
}
/// <summary>
/// EmitEvent: write the single-line JSON to stdout AND kick off a
/// fire-and-forget background PB write (≤1s). Never blocks the caller;
/// background failures are swallowed to a stderr warning.
/// </summary>
public void EmitEvent(CvdiagEnvelope envelope)
{
string json;
try
{
json = JsonSerializer.Serialize(envelope, CvdiagJsonContext.Default.CvdiagEnvelope);
}
catch (Exception ex)
{
Console.Error.WriteLine($"CVDIAG serialize failed error={ex.Message}");
return;
}
if (_writeToStdout) Console.Out.WriteLine(json);
if (string.IsNullOrEmpty(_pbWriteUrl)) return;
// Fire-and-forget: do not await, do not throw into the boundary.
_ = BackgroundWriteAsync(json);
}
private async Task BackgroundWriteAsync(string json)
{
try
{
using var content = new StringContent(json, Encoding.UTF8, "application/json");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
await _httpClient.PostAsync(_pbWriteUrl, content, cts.Token).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.Error.WriteLine($"CVDIAG pb-write failed error={ex.Message}");
}
}
/// <summary>
/// Closed-world metadata filter: keep only the boundary's schema keys; drop
/// the rest and flag <c>_metadata_dropped</c> (spec §6). If the boundary has
/// no key set (shouldn't happen for data-plane), keep nothing and flag drop.
/// </summary>
private static (Dictionary<string, object?> Metadata, bool Dropped) FilterMetadata(
string wireBoundary, Dictionary<string, object?>? raw)
{
var result = new Dictionary<string, object?>();
if (raw is null || raw.Count == 0) return (result, false);
if (!CvdiagSchema.BoundaryMetadataKeys.TryGetValue(wireBoundary, out var allowed))
{
return (result, true);
}
var allowedSet = new HashSet<string>(allowed, StringComparer.Ordinal);
var dropped = false;
foreach (var (key, value) in raw)
{
if (allowedSet.Contains(key)) result[key] = value;
else dropped = true;
}
return (result, dropped);
}
/// <summary>
/// Filter a raw header bag to the closed 9-key <see cref="EdgeHeaders"/>:
/// deny-list rejected first (deny wins, case-insensitive); non-allow keys
/// dropped; every result carries all 9 keys (absent → null). No cf-ip*
/// wildcard — only exact deny-list entries.
/// </summary>
public static EdgeHeaders FilterEdgeHeaders(IReadOnlyDictionary<string, string?> raw)
{
var kept = new Dictionary<string, string?>(StringComparer.Ordinal);
foreach (var (rawKey, rawValue) in raw)
{
var key = rawKey.ToLowerInvariant();
if (DenyList.Contains(key)) continue; // deny wins
if (!AllowList.Contains(key)) continue; // closed-world
kept[key] = rawValue;
}
string? Get(string k) => kept.TryGetValue(k, out var v) ? v : null;
return new EdgeHeaders
{
CfRay = Get("cf-ray"),
CfMitigated = Get("cf-mitigated"),
CfCacheStatus = Get("cf-cache-status"),
XRailwayEdge = Get("x-railway-edge"),
XRailwayRequestId = Get("x-railway-request-id"),
XHikariTrace = Get("x-hikari-trace"),
RetryAfter = Get("retry-after"),
Via = Get("via"),
Server = Get("server"),
};
}
/// <summary>
/// Trim over-budget envelopes to the tier byte cap and stamp
/// <c>_truncated</c> (spec §7). Metadata string values are trimmed first.
/// </summary>
private void ApplyByteCap(CvdiagEnvelope envelope)
{
var cap = ByteCapByTier[Tier];
if (SerializedSize(envelope) <= cap) return;
envelope.Truncated = true;
foreach (var key in new List<string>(envelope.Metadata.Keys))
{
if (SerializedSize(envelope) <= cap) break;
var value = envelope.Metadata[key];
if (value is string s && s.Length > 64)
{
envelope.Metadata[key] = s[..61] + "...";
}
else if (value is not null && value is not (int or long or double or bool))
{
envelope.Metadata[key] = "[truncated]";
}
}
}
private static int SerializedSize(CvdiagEnvelope envelope)
{
try
{
var json = JsonSerializer.Serialize(envelope, CvdiagJsonContext.Default.CvdiagEnvelope);
return Encoding.UTF8.GetByteCount(json);
}
catch
{
return int.MaxValue;
}
}
/// <summary>
/// Mint a UUIDv7 (RFC 9562): 48-bit Unix-ms timestamp, version nibble 7,
/// variant bits 10. Lowercase hyphenated. Matches the TS mintTestId().
/// </summary>
public static string MintTestId(long? nowMs = null)
{
var bytes = new byte[16];
RandomNumberGenerator.Fill(bytes);
var ts = (ulong)(nowMs ?? NowMs());
bytes[0] = (byte)((ts >> 40) & 0xff);
bytes[1] = (byte)((ts >> 32) & 0xff);
bytes[2] = (byte)((ts >> 24) & 0xff);
bytes[3] = (byte)((ts >> 16) & 0xff);
bytes[4] = (byte)((ts >> 8) & 0xff);
bytes[5] = (byte)(ts & 0xff);
bytes[6] = (byte)((bytes[6] & 0x0f) | 0x70); // version 7
bytes[8] = (byte)((bytes[8] & 0x3f) | 0x80); // variant 10
var hex = Convert.ToHexString(bytes).ToLowerInvariant();
return $"{hex[..8]}-{hex[8..12]}-{hex[12..16]}-{hex[16..20]}-{hex[20..32]}";
}
/// <summary>Mint a 16-hex span id (8 random bytes).</summary>
public static string MintSpanId()
{
var bytes = new byte[8];
RandomNumberGenerator.Fill(bytes);
return Convert.ToHexString(bytes).ToLowerInvariant();
}
private long MonoNs() => (long)(_mono.Elapsed.TotalMilliseconds * 1e6);
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private static IReadOnlyDictionary<string, string?> ReadProcessEnv()
{
var dict = new Dictionary<string, string?>(StringComparer.Ordinal);
foreach (System.Collections.DictionaryEntry e in Environment.GetEnvironmentVariables())
{
if (e.Key is string k) dict[k] = e.Value as string;
}
return dict;
}
}
@@ -0,0 +1,324 @@
// <auto-generated>
// CvdiagSchema.cs — C# binding for the CVDIAG flap-observability envelope.
// CODEGEN'd from the single IR `showcase/harness/src/cvdiag/schema.json`
// ($id https://copilotkit.ai/schemas/cvdiag/v1.json, schema_version 1).
// Plan unit L0-D; spec §5. Mirrors the canonical TS `schema.ts` / Pydantic /
// Java bindings so all five languages share one wire contract.
//
// Source of truth: schema.json. To regenerate, re-run the .NET codegen step
// (see L1-F build-context note in the slot report) — DO NOT hand-edit the
// enum members / record fields below; edit schema.ts (→ schema.json) and
// re-emit. The 3 enums, the EdgeHeaders record, the CvdiagEnvelope record,
// the 29 per-boundary metadata records, and the closed key tables below are
// derived 1:1 from schema.json `$defs`.
//
// System.Text.Json source-generation: `CvdiagJsonContext` carries the
// [JsonSerializable] attributes so serialization is AOT/trim-safe and never
// uses reflection-based serializers at runtime.
// </auto-generated>
#nullable enable
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Copilotkit.Showcase.Cvdiag;
/// <summary>Emitting layer (schema.json `$defs.layers`).</summary>
[JsonConverter(typeof(JsonStringEnumConverter<CvdiagLayer>))]
public enum CvdiagLayer
{
[JsonStringEnumMemberName("probe")] Probe,
[JsonStringEnumMemberName("backend")] Backend,
[JsonStringEnumMemberName("aimock")] Aimock,
}
/// <summary>Event outcome (schema.json `$defs.outcomes`).</summary>
[JsonConverter(typeof(JsonStringEnumConverter<CvdiagOutcome>))]
public enum CvdiagOutcome
{
[JsonStringEnumMemberName("ok")] Ok,
[JsonStringEnumMemberName("err")] Err,
[JsonStringEnumMemberName("timeout")] Timeout,
[JsonStringEnumMemberName("info")] Info,
}
/// <summary>
/// Closed boundary enum (schema.json `$defs.boundaries`, 33 members). The
/// dotted wire values (e.g. "backend.response.complete") are carried by the
/// [JsonStringEnumMemberName] attributes; the C# member names are the
/// PascalCase'd, dot-stripped equivalents.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<CvdiagBoundary>))]
public enum CvdiagBoundary
{
[JsonStringEnumMemberName("probe.start")] ProbeStart,
[JsonStringEnumMemberName("probe.navigate.complete")] ProbeNavigateComplete,
[JsonStringEnumMemberName("probe.message.send")] ProbeMessageSend,
[JsonStringEnumMemberName("probe.dom.container.mount")] ProbeDomContainerMount,
[JsonStringEnumMemberName("probe.dom.firsttoken")] ProbeDomFirsttoken,
[JsonStringEnumMemberName("probe.dom.alternate_content")] ProbeDomAlternateContent,
[JsonStringEnumMemberName("probe.sse.event")] ProbeSseEvent,
[JsonStringEnumMemberName("probe.sse.aborted")] ProbeSseAborted,
[JsonStringEnumMemberName("probe.network.error")] ProbeNetworkError,
[JsonStringEnumMemberName("probe.network.response")] ProbeNetworkResponse,
[JsonStringEnumMemberName("probe.console.error")] ProbeConsoleError,
[JsonStringEnumMemberName("probe.exit")] ProbeExit,
[JsonStringEnumMemberName("backend.request.ingress")] BackendRequestIngress,
[JsonStringEnumMemberName("backend.agent.enter")] BackendAgentEnter,
[JsonStringEnumMemberName("backend.llm.call.start")] BackendLlmCallStart,
[JsonStringEnumMemberName("backend.llm.call.heartbeat")] BackendLlmCallHeartbeat,
[JsonStringEnumMemberName("backend.llm.call.response")] BackendLlmCallResponse,
[JsonStringEnumMemberName("backend.sse.first_byte")] BackendSseFirstByte,
[JsonStringEnumMemberName("backend.sse.event")] BackendSseEvent,
[JsonStringEnumMemberName("backend.sse.aborted")] BackendSseAborted,
[JsonStringEnumMemberName("backend.agent.exit")] BackendAgentExit,
[JsonStringEnumMemberName("backend.response.complete")] BackendResponseComplete,
[JsonStringEnumMemberName("backend.error.caught")] BackendErrorCaught,
[JsonStringEnumMemberName("aimock.request.ingress")] AimockRequestIngress,
[JsonStringEnumMemberName("aimock.match.decision")] AimockMatchDecision,
[JsonStringEnumMemberName("aimock.response.start")] AimockResponseStart,
[JsonStringEnumMemberName("aimock.sse.chunk")] AimockSseChunk,
[JsonStringEnumMemberName("aimock.response.aborted")] AimockResponseAborted,
[JsonStringEnumMemberName("aimock.response.complete")] AimockResponseComplete,
[JsonStringEnumMemberName("cvdiag.purge_audit")] CvdiagPurgeAudit,
[JsonStringEnumMemberName("cvdiag.collision_detected")] CvdiagCollisionDetected,
[JsonStringEnumMemberName("cvdiag.queue_dropped")] CvdiagQueueDropped,
[JsonStringEnumMemberName("cvdiag.metadata_dropped")] CvdiagMetadataDropped,
}
/// <summary>
/// The closed 9-key edge-header bag (schema.json `edge_headers`). Every field
/// is always present on the wire; an absent header serializes as null. The
/// hyphenated wire names are carried by [JsonPropertyName].
/// </summary>
public sealed record EdgeHeaders
{
[JsonPropertyName("cf-ray")] public string? CfRay { get; init; }
[JsonPropertyName("cf-mitigated")] public string? CfMitigated { get; init; }
[JsonPropertyName("cf-cache-status")] public string? CfCacheStatus { get; init; }
[JsonPropertyName("x-railway-edge")] public string? XRailwayEdge { get; init; }
[JsonPropertyName("x-railway-request-id")] public string? XRailwayRequestId { get; init; }
[JsonPropertyName("x-hikari-trace")] public string? XHikariTrace { get; init; }
[JsonPropertyName("retry-after")] public string? RetryAfter { get; init; }
[JsonPropertyName("via")] public string? Via { get; init; }
[JsonPropertyName("server")] public string? Server { get; init; }
/// <summary>Always 9 — the closed allow-list cardinality.</summary>
public int KeyCount() => 9;
/// <summary>All 9 field values (for deny-leakage assertions/inspection).</summary>
public IReadOnlyList<string?> AllValues() => new[]
{
CfRay, CfMitigated, CfCacheStatus, XRailwayEdge, XRailwayRequestId,
XHikariTrace, RetryAfter, Via, Server,
};
}
/// <summary>
/// The CVDIAG envelope (schema.json root). Field/property names map to the
/// snake_case wire keys via [JsonPropertyName]; the two optional `_`-prefixed
/// stamp flags are written only when true (DefaultIgnoreCondition handles the
/// false/null case via the source-gen context options).
/// </summary>
public sealed record CvdiagEnvelope
{
[JsonPropertyName("schema_version")] public int SchemaVersion { get; init; } = CvdiagSchema.SchemaVersion;
[JsonPropertyName("test_id")] public string TestId { get; init; } = "";
[JsonPropertyName("trace_id")] public string TraceId { get; init; } = "";
[JsonPropertyName("span_id")] public string SpanId { get; init; } = "";
[JsonPropertyName("parent_span_id")] public string? ParentSpanId { get; init; }
[JsonPropertyName("layer")] public CvdiagLayer Layer { get; init; }
[JsonPropertyName("boundary")] public CvdiagBoundary Boundary { get; init; }
[JsonPropertyName("slug")] public string Slug { get; init; } = "";
[JsonPropertyName("demo")] public string Demo { get; init; } = "";
[JsonPropertyName("ts")] public string Ts { get; init; } = "";
[JsonPropertyName("mono_ns")] public long MonoNs { get; init; }
[JsonPropertyName("duration_ms")] public long? DurationMs { get; init; }
[JsonPropertyName("outcome")] public CvdiagOutcome Outcome { get; init; }
[JsonPropertyName("edge_headers")] public EdgeHeaders EdgeHeaders { get; init; } = new();
[JsonPropertyName("metadata")] public Dictionary<string, object?> Metadata { get; init; } = new();
[JsonPropertyName("_metadata_dropped")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool MetadataDropped { get; set; }
[JsonPropertyName("_truncated")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool Truncated { get; set; }
}
// ── Per-boundary metadata records (29; schema.json `$defs.boundary_metadata_keys`) ──
//
// One record per metadata-bearing boundary. These are convenience/typed views
// over the boundary's closed key set; the on-wire `metadata` bag stays an open
// JSON object (Dictionary<string, object?>) so a single envelope type carries
// every boundary. Emitters MAY construct these and pass their property bag to
// CvdiagEmitArgs.Metadata. Field names mirror the schema.json metadata keys.
public sealed record ProbeStartMeta { public string? Url { get; init; } public string? Viewport { get; init; } }
public sealed record ProbeNavigateCompleteMeta { public string? Url { get; init; } public long? NavMs { get; init; } public int? HttpStatus { get; init; } }
public sealed record ProbeMessageSendMeta { public int? MessageIndex { get; init; } public int? CharCount { get; init; } public string? Demo { get; init; } }
public sealed record ProbeDomContainerMountMeta { public long? DeltaMsFromStart { get; init; } }
public sealed record ProbeDomFirsttokenMeta { public long? DeltaMsFromStart { get; init; } public int? TextLength { get; init; } }
public sealed record ProbeDomAlternateContentMeta { public Dictionary<string, int>? ChildTypeHistogram { get; init; } }
public sealed record ProbeSseEventMeta { public string? EventType { get; init; } public int? PayloadSizeBytes { get; init; } public int? SequenceNum { get; init; } }
public sealed record ProbeSseAbortedMeta { public string? TerminationKind { get; init; } public int? BytesBeforeAbort { get; init; } }
public sealed record ProbeNetworkErrorMeta { public string? Url { get; init; } public string? ErrorClass { get; init; } public int? ResponseStatus { get; init; } }
public sealed record ProbeNetworkResponseMeta { public string? Url { get; init; } public int? Status { get; init; } public int? ContentLength { get; init; } public long? DurationMs { get; init; } }
public sealed record ProbeConsoleErrorMeta { public string? Level { get; init; } public string? MessageScrubbed { get; init; } public string? SourceFile { get; init; } public string? LineCol { get; init; } }
public sealed record ProbeExitMeta { public string? TerminalOutcome { get; init; } public long? TotalDurationMs { get; init; } public int? SseEventCount { get; init; } public long? FirstTokenDeltaMs { get; init; } }
public sealed record BackendRequestIngressMeta { public string? Method { get; init; } public string? Path { get; init; } public int? ContentLength { get; init; } }
public sealed record BackendAgentEnterMeta { public string? AgentName { get; init; } public string? ModelId { get; init; } }
public sealed record BackendLlmCallStartMeta { public string? Provider { get; init; } public string? Model { get; init; } public int? PromptTokenCountEstimate { get; init; } }
public sealed record BackendLlmCallHeartbeatMeta { public long? ElapsedMsSinceStart { get; init; } }
public sealed record BackendLlmCallResponseMeta { public string? Provider { get; init; } public string? Model { get; init; } public int? ResponseTokenCount { get; init; } public long? LatencyMs { get; init; } public string? ErrorClass { get; init; } }
public sealed record BackendSseFirstByteMeta { public long? DeltaMsFromIngress { get; init; } }
public sealed record BackendSseEventMeta { public string? EventType { get; init; } public int? PayloadSizeBytes { get; init; } public int? SequenceNum { get; init; } }
public sealed record BackendSseAbortedMeta { public string? TerminationKind { get; init; } public int? BytesBeforeAbort { get; init; } }
public sealed record BackendAgentExitMeta { public string? TerminalOutcome { get; init; } public long? TotalDurationMs { get; init; } }
public sealed record BackendResponseCompleteMeta { public int? HttpStatus { get; init; } public int? ContentLength { get; init; } public long? TotalDurationMs { get; init; } public int? SseEventCount { get; init; } }
public sealed record BackendErrorCaughtMeta { public string? ExceptionType { get; init; } public string? MessageScrubbed { get; init; } public string? StackBrief { get; init; } public bool? Truncated { get; init; } }
public sealed record AimockRequestIngressMeta { public string? Path { get; init; } public int? ContentLength { get; init; } public string? MatchKeys { get; init; } }
public sealed record AimockMatchDecisionMeta { public string? FixtureId { get; init; } public double? MatchScore { get; init; } public IReadOnlyList<string>? RejectReasons { get; init; } }
public sealed record AimockResponseStartMeta { public long? DeltaMsFromIngress { get; init; } }
public sealed record AimockSseChunkMeta { public int? ChunkSizeBytes { get; init; } public int? SequenceNum { get; init; } }
public sealed record AimockResponseAbortedMeta { public string? TerminationKind { get; init; } public int? BytesBeforeAbort { get; init; } }
public sealed record AimockResponseCompleteMeta { public int? HttpStatus { get; init; } public int? TotalBytes { get; init; } public long? TotalDurationMs { get; init; } public int? ChunkCount { get; init; } }
/// <summary>
/// Static schema tables derived 1:1 from schema.json `$defs`. These back the
/// closed-world metadata filter in <see cref="CvdiagEmitter"/> and the
/// codegen-coverage assertions.
/// </summary>
public static class CvdiagSchema
{
/// <summary>schema.json `schema_version` (const 1).</summary>
public const int SchemaVersion = 1;
/// <summary>schema.json `$defs.edge_header_keys` (9 keys, wire order).</summary>
public static readonly IReadOnlyList<string> EdgeHeaderKeys = new[]
{
"cf-ray", "cf-mitigated", "cf-cache-status", "x-railway-edge",
"x-railway-request-id", "x-hikari-trace", "retry-after", "via", "server",
};
/// <summary>schema.json `$defs.boundaries` (all 33 wire values).</summary>
public static readonly IReadOnlyList<string> AllBoundaries = new[]
{
"probe.start", "probe.navigate.complete", "probe.message.send",
"probe.dom.container.mount", "probe.dom.firsttoken",
"probe.dom.alternate_content", "probe.sse.event", "probe.sse.aborted",
"probe.network.error", "probe.network.response", "probe.console.error",
"probe.exit", "backend.request.ingress", "backend.agent.enter",
"backend.llm.call.start", "backend.llm.call.heartbeat",
"backend.llm.call.response", "backend.sse.first_byte", "backend.sse.event",
"backend.sse.aborted", "backend.agent.exit", "backend.response.complete",
"backend.error.caught", "aimock.request.ingress", "aimock.match.decision",
"aimock.response.start", "aimock.sse.chunk", "aimock.response.aborted",
"aimock.response.complete", "cvdiag.purge_audit",
"cvdiag.collision_detected", "cvdiag.queue_dropped",
"cvdiag.metadata_dropped",
};
/// <summary>
/// schema.json `$defs.boundary_metadata_keys` — the closed metadata key
/// set per data-plane boundary (29 entries; the 4 `cvdiag.*` accounting
/// boundaries carry no closed key set and ride their payload verbatim).
/// Keyed by the dotted wire boundary value.
/// </summary>
public static readonly IReadOnlyDictionary<string, IReadOnlyList<string>> BoundaryMetadataKeys =
new Dictionary<string, IReadOnlyList<string>>
{
["probe.start"] = new[] { "url", "viewport" },
["probe.navigate.complete"] = new[] { "url", "nav_ms", "http_status" },
["probe.message.send"] = new[] { "message_index", "char_count", "demo" },
["probe.dom.container.mount"] = new[] { "delta_ms_from_start" },
["probe.dom.firsttoken"] = new[] { "delta_ms_from_start", "text_length" },
["probe.dom.alternate_content"] = new[] { "child_type_histogram" },
["probe.sse.event"] = new[] { "event_type", "payload_size_bytes", "sequence_num" },
["probe.sse.aborted"] = new[] { "termination_kind", "bytes_before_abort" },
["probe.network.error"] = new[] { "url", "error_class", "response_status" },
["probe.network.response"] = new[] { "url", "status", "content_length", "duration_ms" },
["probe.console.error"] = new[] { "level", "message_scrubbed", "source_file", "line_col" },
["probe.exit"] = new[] { "terminal_outcome", "total_duration_ms", "sse_event_count", "first_token_delta_ms" },
["backend.request.ingress"] = new[] { "method", "path", "content_length" },
["backend.agent.enter"] = new[] { "agent_name", "model_id" },
["backend.llm.call.start"] = new[] { "provider", "model", "prompt_token_count_estimate" },
["backend.llm.call.heartbeat"] = new[] { "elapsed_ms_since_start" },
["backend.llm.call.response"] = new[] { "provider", "model", "response_token_count", "latency_ms", "error_class" },
["backend.sse.first_byte"] = new[] { "delta_ms_from_ingress" },
["backend.sse.event"] = new[] { "event_type", "payload_size_bytes", "sequence_num" },
["backend.sse.aborted"] = new[] { "termination_kind", "bytes_before_abort" },
["backend.agent.exit"] = new[] { "terminal_outcome", "total_duration_ms" },
["backend.response.complete"] = new[] { "http_status", "content_length", "total_duration_ms", "sse_event_count" },
["backend.error.caught"] = new[] { "exception_type", "message_scrubbed", "stack_brief", "truncated" },
["aimock.request.ingress"] = new[] { "path", "content_length", "match_keys" },
["aimock.match.decision"] = new[] { "fixture_id", "match_score", "reject_reasons" },
["aimock.response.start"] = new[] { "delta_ms_from_ingress" },
["aimock.sse.chunk"] = new[] { "chunk_size_bytes", "sequence_num" },
["aimock.response.aborted"] = new[] { "termination_kind", "bytes_before_abort" },
["aimock.response.complete"] = new[] { "http_status", "total_bytes", "total_duration_ms", "chunk_count" },
};
/// <summary>The dotted wire value for a boundary enum member.</summary>
public static string WireValue(CvdiagBoundary boundary) => boundary switch
{
CvdiagBoundary.ProbeStart => "probe.start",
CvdiagBoundary.ProbeNavigateComplete => "probe.navigate.complete",
CvdiagBoundary.ProbeMessageSend => "probe.message.send",
CvdiagBoundary.ProbeDomContainerMount => "probe.dom.container.mount",
CvdiagBoundary.ProbeDomFirsttoken => "probe.dom.firsttoken",
CvdiagBoundary.ProbeDomAlternateContent => "probe.dom.alternate_content",
CvdiagBoundary.ProbeSseEvent => "probe.sse.event",
CvdiagBoundary.ProbeSseAborted => "probe.sse.aborted",
CvdiagBoundary.ProbeNetworkError => "probe.network.error",
CvdiagBoundary.ProbeNetworkResponse => "probe.network.response",
CvdiagBoundary.ProbeConsoleError => "probe.console.error",
CvdiagBoundary.ProbeExit => "probe.exit",
CvdiagBoundary.BackendRequestIngress => "backend.request.ingress",
CvdiagBoundary.BackendAgentEnter => "backend.agent.enter",
CvdiagBoundary.BackendLlmCallStart => "backend.llm.call.start",
CvdiagBoundary.BackendLlmCallHeartbeat => "backend.llm.call.heartbeat",
CvdiagBoundary.BackendLlmCallResponse => "backend.llm.call.response",
CvdiagBoundary.BackendSseFirstByte => "backend.sse.first_byte",
CvdiagBoundary.BackendSseEvent => "backend.sse.event",
CvdiagBoundary.BackendSseAborted => "backend.sse.aborted",
CvdiagBoundary.BackendAgentExit => "backend.agent.exit",
CvdiagBoundary.BackendResponseComplete => "backend.response.complete",
CvdiagBoundary.BackendErrorCaught => "backend.error.caught",
CvdiagBoundary.AimockRequestIngress => "aimock.request.ingress",
CvdiagBoundary.AimockMatchDecision => "aimock.match.decision",
CvdiagBoundary.AimockResponseStart => "aimock.response.start",
CvdiagBoundary.AimockSseChunk => "aimock.sse.chunk",
CvdiagBoundary.AimockResponseAborted => "aimock.response.aborted",
CvdiagBoundary.AimockResponseComplete => "aimock.response.complete",
CvdiagBoundary.CvdiagPurgeAudit => "cvdiag.purge_audit",
CvdiagBoundary.CvdiagCollisionDetected => "cvdiag.collision_detected",
CvdiagBoundary.CvdiagQueueDropped => "cvdiag.queue_dropped",
CvdiagBoundary.CvdiagMetadataDropped => "cvdiag.metadata_dropped",
_ => throw new ArgumentOutOfRangeException(nameof(boundary), boundary, null),
};
}
/// <summary>
/// System.Text.Json source-generation context. Carries the [JsonSerializable]
/// roots so serialization is reflection-free (AOT/trim-safe). The snake_case
/// policy is NOT applied globally — every field already declares its exact
/// wire name via [JsonPropertyName], so we keep the default naming and rely on
/// the explicit attributes (avoids double-transforming hyphenated edge keys).
///
/// IMPORTANT: we do NOT set a context-wide DefaultIgnoreCondition. The schema
/// REQUIRES the nullable fields (`parent_span_id`, `duration_ms`) to appear on
/// the wire as `null`, so a global WhenWritingDefault/WhenWritingNull would
/// drop them and break the closed-world `required` contract. The only fields
/// that should be omitted when default are the two `_`-prefixed stamp flags,
/// which carry their own per-property [JsonIgnore(WhenWritingDefault)].
/// </summary>
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(CvdiagEnvelope))]
[JsonSerializable(typeof(EdgeHeaders))]
[JsonSerializable(typeof(Dictionary<string, object?>))]
public partial class CvdiagJsonContext : JsonSerializerContext
{
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--
Standalone xUnit test project for the shared .NET CVDIAG binding (plan unit
L0-D). The `_shared/dotnet/` directory is a SOURCE-INCLUDE shared library
(no production .csproj of its own — see the L1-F reference note in the slot
report), so this test project compiles the two binding sources directly via
<Compile Include> rather than a <ProjectReference>. This keeps the binding
buildable/testable in isolation without a host integration.
System.Text.Json source-generation requires C# 9+ partial-class contexts and
the JsonStringEnumMemberName attribute (net8.0+); we target net9.0 to match
the .NET integrations (ProverbsAgent.csproj is net9.0).
-->
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>Copilotkit.Showcase.Cvdiag.Tests</RootNamespace>
<AssemblyName>Cvdiag.Schema.Tests</AssemblyName>
</PropertyGroup>
<!-- Compile the shared binding sources directly (source-include model). -->
<ItemGroup>
<Compile Include="..\CvdiagSchema.cs" />
<Compile Include="..\CvdiagEmitter.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
@@ -0,0 +1,262 @@
// CvdiagSchema.test.cs — xUnit red-green proof for the .NET CVDIAG codegen
// binding (plan unit L0-D, spec §5/§6). Mirrors the TS `schema.test.ts` /
// `emit.ts` contract:
//
// 1. schema round-trip — a fully-populated CvdiagEnvelope serializes to JSON
// and deserializes back identically (System.Text.Json source-gen).
// 2. forbidden-header rejection — CvdiagEmitter.FilterEdgeHeaders() drops a
// DENY-list header (cf-ipcountry) and the cf-ip* family even if it is
// injected alongside an allow-list header; every result carries all 9
// allow-list keys (absent → null).
// 3. _metadata_dropped stamp — an envelope carrying an unknown metadata key
// is closed-world filtered and the `_metadata_dropped` flag is set.
// 4. production-env DEBUG refusal — CvdiagEmitter fail-closes (throws) when
// DEBUG is requested in a production environment, when no env resolves,
// and when the allow-list is absent.
//
// RED: with no CvdiagSchema.cs / CvdiagEmitter.cs these types do not exist and
// the suite does not compile. GREEN once both are implemented.
//
// Run (from repo root, requires the .NET 9 SDK):
// dotnet test showcase/integrations/_shared/dotnet/tests/CvdiagSchema.Tests.csproj
using System.Text.Json;
using Copilotkit.Showcase.Cvdiag;
using Xunit;
namespace Copilotkit.Showcase.Cvdiag.Tests;
public class CvdiagSchemaTests
{
private static EdgeHeaders EmptyEdgeHeaders() => new();
private static CvdiagEnvelope SampleEnvelope() => new()
{
SchemaVersion = CvdiagSchema.SchemaVersion,
TestId = "017f22e2-79b0-7cc3-98c4-dc0c0c07398f",
TraceId = "017f22e2-79b0-7cc3-98c4-dc0c0c07398f",
SpanId = "00f067aa0ba902b7",
ParentSpanId = null,
Layer = CvdiagLayer.Backend,
Boundary = CvdiagBoundary.BackendResponseComplete,
Slug = "gen-ui-chat",
Demo = "default",
Ts = "2026-06-18T12:00:00.000Z",
MonoNs = 123456789,
DurationMs = 42,
Outcome = CvdiagOutcome.Ok,
EdgeHeaders = EmptyEdgeHeaders(),
Metadata = new Dictionary<string, object?> { ["http_status"] = 200 },
};
// (1) Round-trip: serialize → deserialize → equal field-by-field.
[Fact]
public void Envelope_RoundTrips_ThroughSystemTextJson()
{
var original = SampleEnvelope();
var json = JsonSerializer.Serialize(original, CvdiagJsonContext.Default.CvdiagEnvelope);
var back = JsonSerializer.Deserialize(json, CvdiagJsonContext.Default.CvdiagEnvelope)!;
Assert.Equal(original.SchemaVersion, back.SchemaVersion);
Assert.Equal(original.TestId, back.TestId);
Assert.Equal(original.SpanId, back.SpanId);
Assert.Equal(original.Layer, back.Layer);
Assert.Equal(original.Boundary, back.Boundary);
Assert.Equal(original.Slug, back.Slug);
Assert.Equal(original.Outcome, back.Outcome);
Assert.Equal(original.MonoNs, back.MonoNs);
Assert.Equal(original.DurationMs, back.DurationMs);
}
// (1b) Wire format: enum + key names match the schema.json contract exactly
// (snake_case keys, dotted/lowercase enum string values).
[Fact]
public void Envelope_SerializesWithSchemaWireNames()
{
var json = JsonSerializer.Serialize(SampleEnvelope(), CvdiagJsonContext.Default.CvdiagEnvelope);
Assert.Contains("\"schema_version\":1", json);
Assert.Contains("\"test_id\":", json);
Assert.Contains("\"parent_span_id\":null", json);
Assert.Contains("\"edge_headers\":", json);
Assert.Contains("\"layer\":\"backend\"", json);
Assert.Contains("\"boundary\":\"backend.response.complete\"", json);
Assert.Contains("\"outcome\":\"ok\"", json);
// cf-ray etc. serialize with their hyphenated wire names.
Assert.Contains("\"cf-ray\":null", json);
Assert.Contains("\"x-railway-request-id\":null", json);
}
// (2) Forbidden-header rejection: cf-ipcountry (DENY) is dropped even when
// injected next to an allow-list header; the cf-ip* family is never
// captured; all 9 allow-list keys are present (absent → null).
[Fact]
public void FilterEdgeHeaders_RejectsDenyListAndCfIpFamily()
{
var raw = new Dictionary<string, string?>
{
["cf-ray"] = "8abc-DFW", // allow → kept
["cf-ipcountry"] = "US", // deny → dropped
["cf-connecting-ip"] = "1.2.3.4", // deny → dropped
["true-client-ip"] = "1.2.3.4", // deny → dropped
["x-forwarded-for"] = "1.2.3.4", // deny → dropped
["CF-IPCity"] = "Dallas", // deny (case-insensitive)
["server"] = "railway-edge", // allow → kept
};
var filtered = CvdiagEmitter.FilterEdgeHeaders(raw);
Assert.Equal("8abc-DFW", filtered.CfRay);
Assert.Equal("railway-edge", filtered.Server);
// DENY-list values never surface on any of the 9 fields.
Assert.DoesNotContain("US", filtered.AllValues());
Assert.DoesNotContain("1.2.3.4", filtered.AllValues());
Assert.DoesNotContain("Dallas", filtered.AllValues());
// All 9 allow-list keys present; unset → null.
Assert.Null(filtered.CfMitigated);
Assert.Null(filtered.XRailwayRequestId);
Assert.Null(filtered.RetryAfter);
Assert.Equal(9, filtered.KeyCount());
}
// (2b) Deny wins even if a deny key somehow appears in the allow path.
[Fact]
public void FilterEdgeHeaders_DenyWinsOverAllow()
{
var raw = new Dictionary<string, string?> { ["forwarded"] = "for=1.2.3.4" };
var filtered = CvdiagEmitter.FilterEdgeHeaders(raw);
Assert.DoesNotContain("for=1.2.3.4", filtered.AllValues());
}
// (3) _metadata_dropped stamp: an unknown metadata key for the boundary is
// dropped (closed-world) and the flag is stamped.
[Fact]
public void Emit_StampsMetadataDropped_OnUnknownKey()
{
var emitter = CvdiagEmitterTestFactory.Verbose();
var env = emitter.Emit(new CvdiagEmitArgs
{
Layer = CvdiagLayer.Backend,
Boundary = CvdiagBoundary.BackendResponseComplete,
Slug = "gen-ui-chat",
Demo = "default",
Outcome = CvdiagOutcome.Ok,
Metadata = new Dictionary<string, object?>
{
["http_status"] = 200, // allowed for this boundary
["totally_unknown_key"] = 1, // not in the closed key set → dropped
},
});
Assert.NotNull(env);
Assert.True(env!.MetadataDropped);
Assert.True(env.Metadata.ContainsKey("http_status"));
Assert.False(env.Metadata.ContainsKey("totally_unknown_key"));
}
// (3b) Clean metadata leaves the flag unset.
[Fact]
public void Emit_DoesNotStampMetadataDropped_OnCleanMetadata()
{
var emitter = CvdiagEmitterTestFactory.Verbose();
var env = emitter.Emit(new CvdiagEmitArgs
{
Layer = CvdiagLayer.Backend,
Boundary = CvdiagBoundary.BackendAgentEnter,
Slug = "gen-ui-chat",
Demo = "default",
Outcome = CvdiagOutcome.Ok,
Metadata = new Dictionary<string, object?>
{
["agent_name"] = "proverbs",
["model_id"] = "gpt-4o-mini",
},
});
Assert.NotNull(env);
Assert.False(env!.MetadataDropped);
}
// (4) Production-env DEBUG refusal: SHOWCASE_ENV=production + DEBUG throws.
[Fact]
public void DebugInProduction_FailsClosed()
{
var env = new Dictionary<string, string?>
{
["SHOWCASE_ENV"] = "production",
["CVDIAG_DEBUG_ALLOW_LIST"] = "gen-ui-chat",
};
Assert.Throws<InvalidOperationException>(
() => new CvdiagEmitter(new CvdiagEmitterOptions { Debug = true, Env = env }));
}
// (4b) Unresolved env (no SHOWCASE_ENV/RAILWAY/ASPNETCORE) treated as prod.
[Fact]
public void DebugWithUnresolvedEnv_FailsClosed()
{
var env = new Dictionary<string, string?> { ["CVDIAG_DEBUG_ALLOW_LIST"] = "gen-ui-chat" };
Assert.Throws<InvalidOperationException>(
() => new CvdiagEmitter(new CvdiagEmitterOptions { Debug = true, Env = env }));
}
// (4c) DEBUG in a non-prod env without an allow-list still refuses.
[Fact]
public void DebugWithoutAllowList_FailsClosed()
{
var env = new Dictionary<string, string?> { ["SHOWCASE_ENV"] = "staging" };
Assert.Throws<InvalidOperationException>(
() => new CvdiagEmitter(new CvdiagEmitterOptions { Debug = true, Env = env }));
}
// (4d) DEBUG in a non-prod env WITH an allow-list is permitted (no throw).
[Fact]
public void DebugInStagingWithAllowList_IsAllowed()
{
var env = new Dictionary<string, string?>
{
["SHOWCASE_ENV"] = "staging",
["CVDIAG_DEBUG_ALLOW_LIST"] = "gen-ui-chat",
};
var emitter = new CvdiagEmitter(new CvdiagEmitterOptions { Debug = true, Env = env });
Assert.Equal(CvdiagTier.Debug, emitter.Tier);
}
// (4e) Tier env precedence: ASPNETCORE_ENVIRONMENT is the last fallback
// (the .NET analogue of NODE_ENV).
[Fact]
public void ResolveEnvLabel_FallsBackToAspNetCoreEnvironment()
{
var env = new Dictionary<string, string?> { ["ASPNETCORE_ENVIRONMENT"] = "Production" };
Assert.Equal("production", CvdiagEmitter.ResolveEnvLabel(env));
}
[Fact]
public void ResolveEnvLabel_ShowcaseEnvWins()
{
var env = new Dictionary<string, string?>
{
["SHOWCASE_ENV"] = "staging",
["RAILWAY_ENVIRONMENT_NAME"] = "production",
["ASPNETCORE_ENVIRONMENT"] = "Production",
};
Assert.Equal("staging", CvdiagEmitter.ResolveEnvLabel(env));
}
// Codegen coverage: all 29 metadata-bearing boundaries have a closed key set.
[Fact]
public void AllBoundaries_HaveClosedMetadataKeySets()
{
Assert.Equal(33, CvdiagSchema.AllBoundaries.Count);
Assert.Equal(29, CvdiagSchema.BoundaryMetadataKeys.Count);
Assert.Equal(9, CvdiagSchema.EdgeHeaderKeys.Count);
}
}
// Small factory: a VERBOSE-tier emitter with no PB writer for metadata tests.
internal static class CvdiagEmitterTestFactory
{
public static CvdiagEmitter Verbose() => new(new CvdiagEmitterOptions
{
Verbose = true,
Env = new Dictionary<string, string?> { ["SHOWCASE_ENV"] = "staging" },
});
}
@@ -0,0 +1,14 @@
"""conftest.py — put ``showcase/integrations`` on ``sys.path`` so the test suite
can ``import _shared.*`` exactly as the runtime does (``/app`` on PYTHONPATH,
``/app/_shared`` the package).
"""
from __future__ import annotations
import sys
from pathlib import Path
# showcase/integrations/_shared/tests/conftest.py → showcase/integrations
_INTEGRATIONS_DIR = Path(__file__).resolve().parents[2]
if str(_INTEGRATIONS_DIR) not in sys.path:
sys.path.insert(0, str(_INTEGRATIONS_DIR))
@@ -0,0 +1,101 @@
"""test_cvdiag_emit_gate_degrade.py — M5 CR R3 functional regression for C1: the
fail-closed DEBUG degrade must actually SUPPRESS emission.
Background: a prior fix made ``cvdiag_bootstrap.setup()`` DEGRADE
(``_ENABLED=False``, instrumentation disabled) instead of crashing the backend on
a fail-closed DEBUG misconfig. But the emit path never consulted that flag —
``emit_cvdiag`` wrote the ``CVDIAG`` envelope to stdout regardless of
``is_enabled()``. A degraded backend therefore STILL emitted default-tier
boundaries whenever the per-integration gate (``CVDIAG_BACKEND_EMITTER=1``) was
on. ``is_enabled()`` was a dead flag.
RED (pre-fix): a degraded ``setup()`` (``_ENABLED=False``) followed by
``emit_cvdiag(...)`` still writes a ``CVDIAG `` line to stdout.
GREEN (post-fix): the degraded emit writes NOTHING; the normal (enabled) path
still emits, so the gate isn't over-tightened.
Imports the package as ``_shared.*`` to mirror the runtime layout; conftest.py
puts ``showcase/integrations`` on sys.path.
"""
from __future__ import annotations
from _shared import cvdiag_bootstrap
# A valid UUIDv7 test_id (version nibble 7, variant 8/9/a/b).
_VALID_TEST_ID = "018f8b2a-7c3e-7a1b-9f4d-0123456789ab"
_VALID_SPAN_ID = "0123456789abcdef"
def _base_envelope(**overrides):
"""A schema-valid default-tier boundary envelope (backend.agent.enter)."""
env = {
"schema_version": 1,
"test_id": _VALID_TEST_ID,
"trace_id": _VALID_TEST_ID,
"span_id": _VALID_SPAN_ID,
"parent_span_id": None,
"layer": "backend",
"boundary": "backend.agent.enter",
"slug": "langgraph-python",
"demo": "chat",
"ts": "2026-06-18T12:00:00Z",
"mono_ns": 123456789,
"duration_ms": None,
"outcome": "ok",
"edge_headers": {
"cf-ray": None,
"cf-mitigated": None,
"cf-cache-status": None,
"x-railway-edge": None,
"x-railway-request-id": None,
"x-hikari-trace": None,
"retry-after": None,
"via": None,
"server": None,
},
"metadata": {"agent_name": "weather", "model_id": "claude"},
}
env.update(overrides)
return env
def test_degraded_setup_suppresses_emission(capsys):
"""A degraded backend (``_ENABLED=False``) must emit NOTHING.
Reproduces the prod-reachable path ``CVDIAG_BACKEND_EMITTER=1`` +
``CVDIAG_DEBUG=1`` (unresolved env → fail-closed degrade): the per-integration
gate is armed, but the shared bootstrap degraded instrumentation OFF. The
shared ``emit_cvdiag`` is the single chokepoint every integration routes
through, so it must honor ``is_enabled()``.
RED (pre-fix): ``emit_cvdiag`` ignores ``_ENABLED`` and still writes the
``CVDIAG `` line.
"""
cvdiag_bootstrap.reset_for_test()
# Degrade: fail-closed DEBUG with an unresolved env disables instrumentation.
cvdiag_bootstrap.setup({"CVDIAG_BACKEND_EMITTER": "1", "CVDIAG_DEBUG": "1"})
assert cvdiag_bootstrap.is_enabled() is False, "precondition: setup degraded"
cvdiag_bootstrap.emit_cvdiag(_base_envelope())
out = capsys.readouterr().out
assert "CVDIAG " not in out, (
"degraded backend must suppress emission; got stdout:\n" + out
)
def test_enabled_setup_still_emits(capsys):
"""Sanity / over-tightening guard: the normal enabled path still emits.
GREEN must not silence a healthy backend — a non-degraded ``setup()`` keeps
``emit_cvdiag`` writing the ``CVDIAG `` envelope line.
"""
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_BACKEND_EMITTER": "1", "SHOWCASE_ENV": "staging"})
assert cvdiag_bootstrap.is_enabled() is True, "precondition: setup enabled"
cvdiag_bootstrap.emit_cvdiag(_base_envelope())
out = capsys.readouterr().out
assert "CVDIAG " in out, "enabled backend must still emit; got stdout:\n" + out
@@ -0,0 +1,133 @@
"""test_cvdiag_inert_when_disabled.py — M5 CR R4 functional regression for N3:
importing the cvdiag bootstrap must be FULLY INERT when cvdiag is disabled.
Background (N3): ``cvdiag_bootstrap.setup()`` ran
``logging.basicConfig(level=…, format=…, force=True)`` unconditionally at import
time (the module calls ``setup()`` at the bottom). ``force=True`` REMOVES and
CLOSES every pre-existing root-logger handler, so merely importing the module —
which every Python integration backend does whenever it imports
``_cvdiag_backend`` — ripped out the HOST application's logging configuration,
even with the backend emitter OFF (``CVDIAG_BACKEND_EMITTER`` unset, the
canary-safe default). That violates the "byte-for-byte inert when disabled /
canary-safe" contract the backend emitters advertise; the canonical TypeScript
emitter performs NO equivalent global mutation.
RED (pre-fix): a pre-configured host root-logger handler is TORN DOWN by the
import-time ``basicConfig(force=True)``.
GREEN (post-fix): importing the bootstrap with cvdiag disabled is fully inert —
the host root handler survives, nothing is written to stdout, and no rogue
non-daemon threads are spawned.
This locks the CLASS of "fully inert when disabled" so future CR rounds stop
re-discovering individual instances of host-state mutation at import.
The assertions run in a FRESH SUBPROCESS so the import-time side effects of
``cvdiag_bootstrap`` are actually observable (a same-process import is cached and
``basicConfig`` is a no-op the second time).
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
# showcase/integrations/_shared/tests/ → showcase/integrations
_INTEGRATIONS_DIR = Path(__file__).resolve().parents[2]
# Child program: pre-configure a host root handler, import the bootstrap with
# cvdiag DISABLED, then assert the import was fully inert. Printed sentinels are
# parsed by the parent so a failure produces a readable diff.
_CHILD = r"""
import logging
import sys
import threading
# (host) Pre-configure the application's own root-logger handler BEFORE importing
# cvdiag — this is the state a real host app has when the backend boots.
_host_handler = logging.StreamHandler(sys.stderr)
_host_handler.set_name("HOST_ROOT_HANDLER")
_root = logging.getLogger()
_root.addHandler(_host_handler)
_root.setLevel(logging.INFO)
_threads_before = {t.ident for t in threading.enumerate()}
import io
_cap = io.StringIO()
_real_stdout = sys.stdout
sys.stdout = _cap
try:
# cvdiag DISABLED: backend emitter OFF (canary-safe default), no debug.
import _shared.cvdiag_bootstrap # noqa: F401 (import-time setup() runs here)
finally:
sys.stdout = _real_stdout
_stdout_text = _cap.getvalue()
# (1) The host's pre-existing root handler MUST survive the import. This is the
# RED trigger on the force=True code (basicConfig(force=True) removed it).
_host_present = any(
getattr(h, "get_name", lambda: None)() == "HOST_ROOT_HANDLER"
for h in logging.getLogger().handlers
)
# (2) Zero CVDIAG lines written to stdout at import (disabled → nothing emitted).
_no_cvdiag = "CVDIAG " not in _stdout_text
# (3) No rogue non-daemon threads spawned by the import.
_new_threads = [
t for t in threading.enumerate()
if t.ident not in _threads_before and not t.daemon
]
_no_nondaemon_threads = len(_new_threads) == 0
print("HOST_PRESENT=" + ("1" if _host_present else "0"))
print("NO_CVDIAG=" + ("1" if _no_cvdiag else "0"))
print("NO_NONDAEMON_THREADS=" + ("1" if _no_nondaemon_threads else "0"))
print("NEW_NONDAEMON_THREADS=" + ",".join(t.name for t in _new_threads))
"""
def _run_child() -> dict[str, str]:
env_clean = {
# Inherit a minimal env but ensure cvdiag is fully disabled.
"PATH": "/usr/bin:/bin",
"PYTHONPATH": str(_INTEGRATIONS_DIR),
# Explicitly NOT setting CVDIAG_BACKEND_EMITTER / CVDIAG_DEBUG /
# CVDIAG_VERBOSE — disabled, canary-safe default.
}
proc = subprocess.run(
[sys.executable, "-c", _CHILD],
capture_output=True,
text=True,
env=env_clean,
cwd=str(_INTEGRATIONS_DIR),
timeout=60,
)
assert proc.returncode == 0, (
"child process failed:\nSTDOUT:\n" + proc.stdout + "\nSTDERR:\n" + proc.stderr
)
result: dict[str, str] = {}
for line in proc.stdout.splitlines():
if "=" in line:
key, _, val = line.partition("=")
result[key] = val
return result
def test_import_is_fully_inert():
"""Importing cvdiag_bootstrap (disabled) must not mutate host logging state."""
r = _run_child()
assert r.get("HOST_PRESENT") == "1", (
"host root-logger handler was torn down by the import-time "
"basicConfig(force=True); the disabled cvdiag bootstrap must be inert"
)
assert r.get("NO_CVDIAG") == "1", (
"a disabled cvdiag import wrote a CVDIAG line to stdout"
)
assert r.get("NO_NONDAEMON_THREADS") == "1", (
"a disabled cvdiag import spawned non-daemon thread(s): "
+ r.get("NEW_NONDAEMON_THREADS", "")
)
@@ -0,0 +1,163 @@
"""test_cvdiag_schema.py — L0-C unit suite for the Python ``_shared`` CVDIAG
bootstrap module (6 tests, spec §5/§6).
Run from the repo root::
python3 -m pytest showcase/integrations/_shared/tests/
These tests import the package as ``_shared.*`` to mirror the runtime layout
(``/app`` on PYTHONPATH, ``/app/_shared`` the package). ``conftest.py`` puts
``showcase/integrations`` on ``sys.path`` so ``import _shared`` resolves.
"""
from __future__ import annotations
import importlib
import logging
import pytest
from _shared import cvdiag_schema as schema
# A valid UUIDv7 test_id (version nibble 7, variant 8/9/a/b).
_VALID_TEST_ID = "018f8b2a-7c3e-7a1b-9f4d-0123456789ab"
_VALID_SPAN_ID = "0123456789abcdef"
def _base_envelope(**overrides):
env = {
"schema_version": 1,
"test_id": _VALID_TEST_ID,
"trace_id": _VALID_TEST_ID,
"span_id": _VALID_SPAN_ID,
"parent_span_id": None,
"layer": "backend",
"boundary": "backend.agent.enter",
"slug": "langgraph-python",
"demo": "chat",
"ts": "2026-06-18T12:00:00Z",
"mono_ns": 123456789,
"duration_ms": None,
"outcome": "ok",
"edge_headers": {
"cf-ray": None,
"cf-mitigated": None,
"cf-cache-status": None,
"x-railway-edge": None,
"x-railway-request-id": None,
"x-hikari-trace": None,
"retry-after": None,
"via": None,
"server": None,
},
"metadata": {"agent_name": "weather", "model_id": "claude"},
}
env.update(overrides)
return env
def test_valid_envelope_round_trips():
"""A well-formed envelope validates and round-trips by alias."""
model = schema.CvdiagEnvelope.model_validate(_base_envelope())
assert model.boundary is schema.CvdiagBoundary.BACKEND_AGENT_ENTER
assert model.layer is schema.CvdiagLayer.BACKEND
assert model.outcome is schema.CvdiagOutcome.OK
assert model.metadata_dropped is False
dumped = model.model_dump(by_alias=True)
# The edge-header alias keys survive the round-trip.
assert dumped["edge_headers"]["cf-ray"] is None
assert dumped["_metadata_dropped"] is False
def test_unknown_metadata_key_stamps_metadata_dropped():
"""An unknown metadata key sets ``_metadata_dropped`` on the envelope."""
env = _base_envelope(
metadata={"agent_name": "weather", "bogus_key": "x"},
)
model = schema.CvdiagEnvelope.model_validate(env)
assert model.metadata_dropped is True
# Also true for an unknown TOP-LEVEL key.
env2 = _base_envelope()
env2["totally_unknown"] = "y"
model2 = schema.CvdiagEnvelope.model_validate(env2)
assert model2.metadata_dropped is True
def test_forbidden_edge_header_dropped():
"""A deny-list edge header (cf-connecting-ip) is rejected by EdgeHeaders.
``EdgeHeaders`` forbids extra keys, so a forbidden header can never round
-trip through the closed model.
"""
bad = {
"cf-ray": "abc",
"cf-mitigated": None,
"cf-cache-status": None,
"x-railway-edge": None,
"x-railway-request-id": None,
"x-hikari-trace": None,
"retry-after": None,
"via": None,
"server": None,
"cf-connecting-ip": "1.2.3.4", # forbidden PII header
}
with pytest.raises(Exception):
schema.EdgeHeaders.model_validate(bad)
def test_uuidv7_test_id_validation():
"""UUIDv7 test_id passes; a UUIDv4 / malformed test_id is rejected."""
# Pass.
schema.CvdiagEnvelope.model_validate(_base_envelope(test_id=_VALID_TEST_ID))
# UUIDv4 (version nibble 4) → reject.
uuid_v4 = "018f8b2a-7c3e-4a1b-9f4d-0123456789ab"
with pytest.raises(Exception):
schema.CvdiagEnvelope.model_validate(_base_envelope(test_id=uuid_v4))
# Malformed → reject.
with pytest.raises(Exception):
schema.CvdiagEnvelope.model_validate(_base_envelope(test_id="not-a-uuid"))
def test_debug_in_production_degrades_at_setup():
"""``CVDIAG_DEBUG=1`` + ``SHOWCASE_ENV=production`` fails closed at setup.
Fail-closed intent: instrumentation is DISABLED (tier stays ``default``,
``is_enabled()`` False). Degrade-not-crash: ``setup()`` must NOT raise —
a misconfig may not abort the backend's module import.
"""
from _shared import cvdiag_bootstrap
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1", "SHOWCASE_ENV": "production"})
assert cvdiag_bootstrap.current_tier() == "default"
assert cvdiag_bootstrap.is_enabled() is False
# Unresolved env is ALSO treated as production (fail-closed → degraded).
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1"})
assert cvdiag_bootstrap.current_tier() == "default"
assert cvdiag_bootstrap.is_enabled() is False
# A non-production env with DEBUG is allowed (instrumentation enabled).
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1", "SHOWCASE_ENV": "staging"})
assert cvdiag_bootstrap.current_tier() == "debug"
assert cvdiag_bootstrap.is_enabled() is True
def test_basicconfig_captures_agents_logger_output(capsys):
"""``setup()`` installs a handler so ``agents.*`` loggers actually emit.
This is the silent-drop regression guard: before bootstrap configures the
root logger, an ``agents._header_forwarding`` ``logger.info`` is dropped;
after ``setup()`` it reaches the stream.
"""
from _shared import cvdiag_bootstrap
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"SHOWCASE_ENV": "staging"})
fwd_logger = logging.getLogger("agents._header_forwarding")
fwd_logger.info("CVDIAG component=test boundary=probe.start status=ok")
captured = capsys.readouterr()
combined = captured.out + captured.err
assert "CVDIAG component=test boundary=probe.start" in combined
@@ -0,0 +1,164 @@
"""test_cvdiag_writer_bootstrap.py — functional regression suite for the three
M5 CR R1 fixes in the Python ``_shared`` CVDIAG core:
FIX-1 cvdiag_pb_writer drain loop never-propagate: a non-JSON-serializable
envelope must NOT kill the flush daemon (mirrors TS pb-writer
writeBatch — one bad row degrades, the batch/daemon survives).
FIX-2 cvdiag_bootstrap.setup() degrade-not-crash: a fail-closed DEBUG
misconfig must DISABLE instrumentation, not raise out of import.
FIX-3 cvdiag_bootstrap.setup() idempotency: a repeated call is a no-op and
never orphans a second daemon thread / PB writer.
Imports the package as ``_shared.*`` to mirror the runtime layout; conftest.py
puts ``showcase/integrations`` on sys.path.
"""
from __future__ import annotations
import threading
import time
from _shared import cvdiag_bootstrap
from _shared.cvdiag_pb_writer import CvdiagPbWriter
class _Unserializable:
"""An object json.dumps cannot encode (raises TypeError in _post)."""
def _drain_thread_alive(writer: CvdiagPbWriter) -> bool:
worker = writer._worker
return worker is not None and worker.is_alive()
# ── FIX-1: drain loop never-propagate ────────────────────────────────────────
def test_drain_survives_non_json_serializable_envelope(monkeypatch):
"""A non-JSON-serializable record must not kill the daemon; a later valid
record still flushes.
RED (pre-fix): ``_post`` lets ``TypeError`` from ``json.dumps`` escape the
``except (URLError, OSError, ValueError)`` clause; the exception unwinds
``_run`` and the daemon thread dies — the later valid envelope is never
POSTed.
"""
writer = CvdiagPbWriter(pb_url="http://pb.invalid", flush_window_s=0.05)
posted: list[dict] = []
# Stub the HTTP layer: record what reaches the wire after json.dumps.
def _fake_post(envelope):
# Re-run the real serialization seam so a bad envelope still throws
# inside the drain loop, but a good one is "delivered" without network.
import json as _json
_json.dumps(envelope) # raises TypeError on the bad envelope
posted.append(envelope)
monkeypatch.setattr(writer, "_post", _fake_post)
# Bad record first — pre-fix this kills the drain thread.
writer.enqueue({"bad": _Unserializable()})
# Give the worker a couple of flush windows to process the bad record.
time.sleep(0.2)
# Then a perfectly valid record.
writer.enqueue({"ok": True, "n": 1})
time.sleep(0.2)
assert _drain_thread_alive(writer), "drain daemon must survive a bad record"
assert {"ok": True, "n": 1} in posted, "valid record must still flush"
def test_post_swallows_typeerror_on_unserializable(monkeypatch):
"""``_post`` itself must not raise on a non-serializable envelope.
RED (pre-fix): the ``TypeError`` from ``json.dumps`` is uncaught and
propagates out of ``_post`` (only URLError/OSError/ValueError are caught).
"""
writer = CvdiagPbWriter(pb_url="http://pb.invalid")
# Should NOT raise — _post is the never-throw persistence seam.
writer._post({"bad": _Unserializable()})
# ── FIX-2: bootstrap degrade-not-crash ───────────────────────────────────────
def test_setup_degrades_on_failclosed_debug_misconfig():
"""A fail-closed DEBUG misconfig DISABLES instrumentation instead of raising.
RED (pre-fix): ``setup({"CVDIAG_DEBUG": "1"})`` (unresolved env → treated as
production) raises ``RuntimeError`` out of setup(), which at import time
would abort the whole backend module import.
"""
cvdiag_bootstrap.reset_for_test()
# Must NOT raise.
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1"})
# Fail-closed intent preserved: instrumentation is OFF (tier not debug).
assert cvdiag_bootstrap.current_tier() == "default"
assert cvdiag_bootstrap.is_enabled() is False
# Explicit production env with DEBUG also degrades, never raises.
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1", "SHOWCASE_ENV": "production"})
assert cvdiag_bootstrap.current_tier() == "default"
assert cvdiag_bootstrap.is_enabled() is False
def test_setup_allows_debug_in_nonproduction():
"""A non-production DEBUG request still enables debug tier (intent intact)."""
cvdiag_bootstrap.reset_for_test()
cvdiag_bootstrap.setup({"CVDIAG_DEBUG": "1", "SHOWCASE_ENV": "staging"})
assert cvdiag_bootstrap.current_tier() == "debug"
assert cvdiag_bootstrap.is_enabled() is True
# ── FIX-3: bootstrap idempotency ─────────────────────────────────────────────
def test_setup_is_idempotent_no_orphan_daemon():
"""A second setup() is a no-op: no second PB writer / daemon thread.
RED (pre-fix): no ``_SETUP_DONE`` guard in setup(), so a second call rebuilds
``_PB_WRITER`` (orphaning the first writer's queue) and a subsequent enqueue
spins a second ``cvdiag-pb-writer`` daemon thread.
"""
cvdiag_bootstrap.reset_for_test()
# Count daemon threads BEFORE this test so the assertion is robust to
# daemons left alive by earlier tests in the suite (short-lived best-effort
# daemons are not joined on reset).
def _pb_daemon_count() -> int:
return sum(1 for t in threading.enumerate() if t.name == "cvdiag-pb-writer")
before = _pb_daemon_count()
cvdiag_bootstrap.setup(
{"SHOWCASE_ENV": "staging", "CVDIAG_PB_URL": "http://pb.invalid"}
)
first_writer = cvdiag_bootstrap._PB_WRITER
assert first_writer is not None
first_writer.enqueue({"n": 1})
time.sleep(0.05)
after_first = _pb_daemon_count()
assert after_first == before + 1, (
"first setup()+enqueue must spin exactly one daemon"
)
cvdiag_bootstrap.setup(
{"SHOWCASE_ENV": "staging", "CVDIAG_PB_URL": "http://pb.invalid"}
)
second_writer = cvdiag_bootstrap._PB_WRITER
assert second_writer is first_writer, (
"second setup() must not rebuild the PB writer"
)
second_writer.enqueue({"n": 2})
time.sleep(0.05)
after_second = _pb_daemon_count()
# The second setup()+enqueue must NOT spin an additional daemon.
assert after_second == after_first, (
f"second setup() orphaned a daemon: {before}->{after_first}->{after_second}"
)
@@ -0,0 +1,248 @@
"""test_cvdiag_writer_live_pb.py — live-PocketBase auth proof for the Python
CVDIAG PB writer.
Boots an ACTUAL PocketBase 0.22 server using the EXACT production migrations
(showcase/pocketbase/pb_migrations), which create ``cvdiag_events`` + the
``cvdiag_api_keys`` auth collection + the seeded role-keyed records (writer /
purge / migration). It then drives the real ``CvdiagPbWriter`` against that
server and asserts the CREATE-only ACL is satisfied **as the writer role**.
WHY WRITER-ROLE (not superuser): the PB superuser bypasses ALL collection
rules, so a writer that never authenticates would still "succeed" if probed as
a superuser — that is exactly how the original defect (the inert
``X-Cvdiag-Writer-Key`` header) escaped review. The createRule
@request.auth.collectionName = "cvdiag_api_keys" && @request.auth.role = "writer"
is only satisfiable by authenticating as the seeded writer record and sending
``Authorization: Bearer <token>``. We therefore drive the writer with the
writer record's PASSWORD as ``CVDIAG_WRITER_KEY`` and verify the row lands by
querying ``cvdiag_events`` AS THE SUPERUSER (read-only, rule-bypassing) — the
write path under test is never superuser.
Requires a ``pocketbase`` binary: set ``POCKETBASE_BIN`` to its path, put it on
PATH, or drop it at ``/tmp/pb022/pocketbase``. The suite SKIPS (does not fail)
when no binary is available so CI without the binary stays green.
"""
from __future__ import annotations
import json
import os
import shutil
import socket
import subprocess
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Optional
import pytest
from _shared.cvdiag_pb_writer import CvdiagPbWriter
# These mirror the seed records created by the cvdiag_api_keys migration
# (showcase/pocketbase/pb_migrations/1779990200_create_cvdiag_events.js).
WRITER_EMAIL = "cvdiag-writer@keys.local"
WRITER_PASS = "cvdiagwriterpass123"
# A superuser used ONLY to read rows back (rule-bypassing read); never the
# write path under test.
ADMIN_EMAIL = "cvdiag-py-acl@test.local"
ADMIN_PASS = "cvdiagpyaclpass123"
# showcase/integrations/_shared/tests/ → repo root → showcase/pocketbase
_REPO_ROOT = Path(__file__).resolve().parents[4]
_MIGRATIONS_DIR = _REPO_ROOT / "showcase" / "pocketbase" / "pb_migrations"
def _resolve_pb_binary() -> Optional[str]:
explicit = os.environ.get("POCKETBASE_BIN")
if explicit and Path(explicit).is_file():
return explicit
on_path = shutil.which("pocketbase")
if on_path:
return on_path
if Path("/tmp/pb022/pocketbase").is_file():
return "/tmp/pb022/pocketbase"
return None
_PB_BIN = _resolve_pb_binary()
pytestmark = pytest.mark.skipif(
_PB_BIN is None,
reason="no pocketbase binary (set POCKETBASE_BIN, PATH, or /tmp/pb022/pocketbase)",
)
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def _wait_for_health(base: str, timeout_s: float = 15.0) -> None:
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
try:
with urllib.request.urlopen(f"{base}/api/health", timeout=1.0) as r:
if r.status == 200:
return
except (urllib.error.URLError, OSError):
pass
time.sleep(0.2)
raise RuntimeError("PocketBase did not become healthy in time")
def _admin_token(base: str) -> str:
body = json.dumps({"identity": ADMIN_EMAIL, "password": ADMIN_PASS}).encode()
req = urllib.request.Request(
f"{base}/api/admins/auth-with-password",
data=body,
method="POST",
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=5.0) as r:
return json.loads(r.read())["token"]
def _count_events(base: str, test_id: str) -> int:
"""Count cvdiag_events rows for one test_id, reading AS SUPERUSER."""
tok = _admin_token(base)
flt = urllib.parse.quote(f'test_id="{test_id}"')
req = urllib.request.Request(
f"{base}/api/collections/cvdiag_events/records?filter={flt}",
headers={"Authorization": tok},
)
with urllib.request.urlopen(req, timeout=5.0) as r:
return json.loads(r.read())["totalItems"]
@pytest.fixture(scope="module")
def live_pb():
"""Boot a real PB with the production migrations; yield its base URL."""
# skipif(_PB_BIN is None) guarantees a binary at runtime; narrow to a
# concrete str so subprocess calls don't take a `str | None` arg.
pb_bin = _PB_BIN
assert pb_bin is not None
data_dir = tempfile.mkdtemp(prefix="pb-cvdiag-py-")
try:
mig = subprocess.run(
[
pb_bin,
"migrate",
"up",
f"--dir={data_dir}",
f"--migrationsDir={_MIGRATIONS_DIR}",
],
capture_output=True,
text=True,
)
if mig.returncode != 0:
raise RuntimeError(f"pb migrate up failed: {mig.stderr or mig.stdout}")
admin = subprocess.run(
[pb_bin, "admin", "create", ADMIN_EMAIL, ADMIN_PASS, f"--dir={data_dir}"],
capture_output=True,
text=True,
)
if admin.returncode != 0:
raise RuntimeError(
f"pb admin create failed: {admin.stderr or admin.stdout}"
)
port = _free_port()
base = f"http://127.0.0.1:{port}"
proc = subprocess.Popen(
[
pb_bin,
"serve",
f"--http=127.0.0.1:{port}",
f"--dir={data_dir}",
f"--migrationsDir={_MIGRATIONS_DIR}",
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
try:
_wait_for_health(base)
yield base
finally:
proc.kill()
proc.wait(timeout=5)
finally:
shutil.rmtree(data_dir, ignore_errors=True)
def _flush_one(writer: CvdiagPbWriter, envelope: dict) -> None:
"""Enqueue one envelope and let the daemon drain it (flush window 0.05s)."""
writer.enqueue(envelope)
# Give the worker a few flush windows to drain + POST + (re-)auth.
time.sleep(1.0)
def _event(test_id: str) -> dict:
return {
"schema_version": 1,
"test_id": test_id,
"trace_id": test_id,
"span_id": "0000000000000001",
"parent_span_id": None,
"layer": "backend",
"boundary": "backend.handle",
"slug": "langgraph-python",
"demo": "chat",
"ts": "2026-06-18T00:00:00.000Z",
"mono_ns": 1,
"duration_ms": None,
"outcome": "ok",
"edge_headers": {},
"metadata": {"src": "live-pb-test"},
}
def test_writer_authenticates_as_writer_role_and_persists(live_pb):
"""GREEN: the writer auth-with-passwords as the writer role → CREATE 201 →
the row is present in cvdiag_events (read back as superuser).
This is the fix for the inert ``X-Cvdiag-Writer-Key`` header: without
auth-with-password → Bearer the CREATE 403s under the writer createRule.
"""
test_id = "0190a0c0-0000-7000-8000-00000000a001"
# Pre-condition: no row yet.
assert _count_events(live_pb, test_id) == 0
writer = CvdiagPbWriter(
pb_url=live_pb,
writer_key=WRITER_PASS, # CVDIAG_WRITER_KEY == the writer record password
flush_window_s=0.05,
)
_flush_one(writer, _event(test_id))
# The CREATE went through the CREATE-only writer rule → row present.
assert _count_events(live_pb, test_id) == 1
def test_writer_with_wrong_password_degrades_to_noop(live_pb):
"""A WRONG password must degrade to a no-op (auth fails, CREATE 401/403)
WITHOUT crashing the daemon — never-throw preserved.
"""
test_id = "0190a0c0-0000-7000-8000-00000000a002"
assert _count_events(live_pb, test_id) == 0
writer = CvdiagPbWriter(
pb_url=live_pb,
writer_key="totally-wrong-password",
flush_window_s=0.05,
)
_flush_one(writer, _event(test_id))
# No row landed (auth failed) AND the daemon survived (no crash).
assert _count_events(live_pb, test_id) == 0
worker = writer._worker
assert worker is not None and worker.is_alive(), (
"daemon must survive an auth failure (never-throw)"
)
@@ -0,0 +1,205 @@
/**
* cvdiag-emitter.test.ts — vitest suite for the shared TS integration emitter
* binding (plan unit L0-F). Asserts the four invariants the §6 PII/tier
* contract requires of EVERY language emitter:
* - schema conformance (re-exported envelope keys + UUIDv7 minters),
* - tier gating (default vs verbose vs debug boundary inclusion),
* - PII scrub (Bearer / sk- secrets removed from captured values),
* - forbidden-header rejection (cf-ipcountry never captured),
* - DEBUG-in-production refusal (fail-closed startup guard).
*
* These re-exercise the L0-A invariants THROUGH the binding so a regression in
* the re-export wiring (wrong relative path, dropped symbol) fails here, not
* silently in a downstream TS integration.
*/
import { describe, expect, it } from "vitest";
import {
CvdiagEmitter,
ENVELOPE_KEYS,
EDGE_HEADER_DENYLIST,
SCHEMA_VERSION,
TEST_ID_REGEX,
filterEdgeHeaders,
isValidTestId,
mintSpanId,
mintTestId,
scrubSecrets,
validateEnvelope,
} from "./cvdiag-emitter.js";
import type { CvdiagEnvelope } from "./cvdiag-emitter.js";
describe("L0-F binding: re-exports resolve from the canonical schema", () => {
it("re-exports SCHEMA_VERSION === 1", () => {
expect(SCHEMA_VERSION).toBe(1);
});
it("re-exports the closed envelope key set and validator", () => {
expect(ENVELOPE_KEYS).toContain("test_id");
expect(ENVELOPE_KEYS).toContain("edge_headers");
// A foreign top-level key is rejected (closed-world).
const bad = validateEnvelope({ test_id: "x", attacker_key: 1 });
expect(bad.ok).toBe(false);
expect(bad.unknownKeys).toContain("attacker_key");
});
it("re-exports the UUIDv7 minters + validator", () => {
const id = mintTestId();
expect(TEST_ID_REGEX.test(id)).toBe(true);
expect(isValidTestId(id)).toBe(true);
// A v4 UUID (version nibble 4) must be rejected.
expect(isValidTestId("00000000-0000-4000-8000-000000000000")).toBe(false);
// span_id is 16 lowercase hex chars.
expect(mintSpanId()).toMatch(/^[0-9a-f]{16}$/);
});
});
describe("L0-F binding: schema conformance of an emitted envelope", () => {
it("emits a closed-world envelope at the verbose tier", () => {
const emitter = new CvdiagEmitter({
verbose: true,
env: {},
layer: "backend",
});
const env = emitter.emit({
layer: "backend",
boundary: "backend.agent.enter",
slug: "langgraph-typescript",
demo: "agentic_chat",
outcome: "ok",
metadata: { agent_name: "main", model_id: "gpt-4o" },
}) as CvdiagEnvelope;
expect(env).not.toBeNull();
expect(env.schema_version).toBe(SCHEMA_VERSION);
expect(isValidTestId(env.test_id)).toBe(true);
expect(env.trace_id).toBe(env.test_id);
expect(env.boundary).toBe("backend.agent.enter");
// Every emitted key must be in the closed envelope key set.
expect(validateEnvelope(env as unknown as Record<string, unknown>).ok).toBe(
true,
);
// All 9 edge-header keys present (absent → null).
expect(Object.keys(env.edge_headers).sort()).toEqual(
[
"cf-cache-status",
"cf-mitigated",
"cf-ray",
"retry-after",
"server",
"via",
"x-hikari-trace",
"x-railway-edge",
"x-railway-request-id",
].sort(),
);
});
it("drops unknown metadata keys and stamps _metadata_dropped", () => {
const emitter = new CvdiagEmitter({
verbose: true,
env: {},
layer: "backend",
});
const env = emitter.emit({
layer: "backend",
boundary: "backend.agent.enter",
slug: "mastra",
demo: "agentic_chat",
outcome: "ok",
metadata: { agent_name: "main", model_id: "gpt-4o", attacker: "x" },
}) as CvdiagEnvelope;
expect(env._metadata_dropped).toBe(true);
expect(env.metadata).not.toHaveProperty("attacker");
});
});
describe("L0-F binding: tier gating", () => {
it("default tier excludes a verbose-only boundary", () => {
const emitter = new CvdiagEmitter({ env: {}, layer: "backend" });
expect(emitter.tier).toBe("default");
// backend.request.ingress is verbose+debug only (default:false).
expect(emitter.shouldEmit("backend.request.ingress")).toBe(false);
// backend.agent.enter is default:true.
expect(emitter.shouldEmit("backend.agent.enter")).toBe(true);
});
it("verbose tier includes verbose-only boundaries", () => {
const emitter = new CvdiagEmitter({
verbose: true,
env: {},
layer: "backend",
});
expect(emitter.tier).toBe("verbose");
expect(emitter.shouldEmit("backend.request.ingress")).toBe(true);
});
it("accounting boundaries always emit regardless of tier", () => {
const emitter = new CvdiagEmitter({ env: {}, layer: "backend" });
expect(emitter.shouldEmit("cvdiag.queue_dropped")).toBe(true);
});
});
describe("L0-F binding: PII scrub (re-exported from edge-headers)", () => {
it("scrubs Bearer tokens", () => {
expect(scrubSecrets("auth: Bearer abc123def456")).toBe("auth: [REDACTED]");
});
it("scrubs sk- provider keys", () => {
expect(scrubSecrets("key sk-ABCDEFGHIJKLMNOP1234")).toBe("key [REDACTED]");
});
});
describe("L0-F binding: forbidden edge-header rejection", () => {
it("never captures cf-ipcountry even when present", () => {
const filtered = filterEdgeHeaders({
"cf-ray": "abc-iad",
"cf-ipcountry": "US",
"true-client-ip": "1.2.3.4",
});
expect(filtered["cf-ray"]).toBe("abc-iad");
expect(filtered).not.toHaveProperty("cf-ipcountry");
expect(filtered).not.toHaveProperty("true-client-ip");
});
it("the deny list contains the cf-ip* family by exact match", () => {
expect(EDGE_HEADER_DENYLIST).toContain("cf-ipcountry");
expect(EDGE_HEADER_DENYLIST).toContain("cf-connecting-ip");
});
});
describe("L0-F binding: DEBUG fail-closed in production", () => {
it("refuses DEBUG when env resolves to production", () => {
expect(
() =>
new CvdiagEmitter({
debug: true,
env: {
SHOWCASE_ENV: "production",
CVDIAG_DEBUG_ALLOW_LIST: "langgraph-typescript",
},
}),
).toThrow(/production/);
});
it("refuses DEBUG when no env label resolves (unknown == prod)", () => {
expect(
() =>
new CvdiagEmitter({
debug: true,
env: { CVDIAG_DEBUG_ALLOW_LIST: "langgraph-typescript" },
}),
).toThrow(/unresolved|production/);
});
it("allows DEBUG in a non-prod env with an allow-list", () => {
const emitter = new CvdiagEmitter({
debug: true,
env: {
SHOWCASE_ENV: "staging",
CVDIAG_DEBUG_ALLOW_LIST: "langgraph-typescript",
},
});
expect(emitter.tier).toBe("debug");
});
});
@@ -0,0 +1,140 @@
/**
* cvdiag-emitter.ts — the SHARED TypeScript CVDIAG emitter binding that the
* four TS-backed integrations import (langgraph-typescript, claude-sdk-typescript,
* mastra, built-in-agent). Plan unit: L0-F. Spec: 2026-06-18-flap-observability
* §5 (schema) + §6 (tiers / PII).
*
* ──────────────────────────────────────────────────────────────────────────
* SINGLE SOURCE OF TRUTH — this file RE-EXPORTS, it does NOT redefine.
*
* The canonical CVDIAG schema, edge-header allow/deny filter, PII scrub, and
* the `CvdiagEmitter` (tier resolution, fail-closed DEBUG guard, byte caps,
* bounded queue, UUIDv7 span/test minters) all live in L0-A under
* `showcase/harness/src/cvdiag/`. This binding is a thin barrel that pulls
* those symbols forward so the TS integrations have ONE import surface and so
* a schema change in L0-A propagates here automatically (no duplicate enum to
* drift). Re-exporting (not duplicating) is the whole point of the unit: the
* §5 "single source of truth" / "CI lint fails on drift" policy is only
* enforceable if every emitter shares the L0-A definitions.
*
* The relative path `../../../harness/src/cvdiag/` resolves as:
* showcase/integrations/_shared/ts/ → ../../../harness/src/cvdiag/
* ( _shared/ts → _shared → integrations → showcase )/harness/src/cvdiag
* i.e. from this file up three levels to `showcase/`, then into `harness/`.
* The `.js` extensions match L0-A's ESM (`"type": "module"`, bundler module
* resolution) — at runtime under tsx / a bundler the `.js` specifier resolves
* to the `.ts` source, exactly as the harness's own `index.ts` barrel does.
*
* ──────────────────────────────────────────────────────────────────────────
* CROSS-CONTEXT RESOLUTION FOR L1-E (how a standalone TS integration's Docker
* build sees these files):
*
* Each TS integration (langgraph-typescript, claude-sdk-typescript, mastra)
* already vendors a sibling-directory `shared-tools/` into its build context
* via a `COPY shared-tools/ ./shared-tools/` line in its Dockerfile (the
* build context is the integration dir, so a sibling source tree is copied
* in as a top-level dir, NOT imported across the repo). L1-E MUST use the
* SAME mechanism for CVDIAG:
*
* 1. Stage `_shared/ts/cvdiag-emitter.ts` (this file, with the relative
* re-exports flattened to point at a co-located copy of the L0-A
* sources) AND the three L0-A sources it pulls from
* (`schema.ts`, `edge-headers.ts`, `emit.ts`) into the integration's
* build context — e.g. under `shared-tools/cvdiag/` or a dedicated
* `_cvdiag/` dir — exactly as `shared-tools/` is copied today.
* 2. Add `COPY shared-tools/cvdiag/ ./shared-tools/cvdiag/` (or the chosen
* path) to each integration Dockerfile, mirroring the existing
* `COPY shared-tools/ ./shared-tools/` line.
* 3. The integration's CVDIAG wiring imports
* `from "../../shared-tools/cvdiag/cvdiag-emitter"` (path per chosen
* layout) rather than reaching across the monorepo — standalone npm
* projects have no path alias back to `showcase/harness`.
*
* This is the TS analogue of L0-C's Python `_shared/cvdiag_bootstrap`: a
* COPY-into-context staging step, NOT a workspace/path-alias import. Within
* THIS slot (the harness/worktree) the relative `../../../harness/...`
* re-export resolves directly so the vitest suite runs against the real L0-A
* sources; L1-E performs the COPY-staging flatten when packaging each
* integration. A `bin/showcase cvdiag stage-ts` helper (or the existing
* build wrapper) is the natural home for the copy, so the staging is a
* build step and not hand-maintained per integration.
*/
// ── Schema (types, enums, validators, UUIDv7 regex) ─────────────────────────
export {
SCHEMA_VERSION,
CVDIAG_LAYERS,
CVDIAG_OUTCOMES,
PROBE_BOUNDARIES,
BACKEND_BOUNDARIES,
AIMOCK_BOUNDARIES,
CVDIAG_DATA_PLANE_BOUNDARIES,
CVDIAG_ACCOUNTING_BOUNDARIES,
CVDIAG_BOUNDARIES,
EDGE_HEADER_KEYS,
TERMINATION_KINDS,
TEST_ID_REGEX,
ENVELOPE_KEYS,
BOUNDARY_METADATA_KEYS,
isValidTestId,
validateEnvelope,
validateMetadata,
} from "../../../harness/src/cvdiag/schema.js";
export type {
CvdiagLayer,
CvdiagOutcome,
CvdiagDataPlaneBoundary,
CvdiagAccountingBoundary,
CvdiagBoundary,
EdgeHeaders,
EdgeHeaderKey,
TerminationKind,
CvdiagEnvelope,
EnvelopeValidationResult,
MetadataValidationResult,
} from "../../../harness/src/cvdiag/schema.js";
// ── Edge-header allow/deny filter + PII scrub ───────────────────────────────
export {
EDGE_HEADER_ALLOWLIST,
EDGE_HEADER_DENYLIST,
BEARER_TOKEN_REGEX,
SK_KEY_REGEX,
URL_USERINFO_REGEX,
SCRUB_REPLACEMENT,
scrubSecrets,
filterEdgeHeaders,
} from "../../../harness/src/cvdiag/edge-headers.js";
// ── Emitter (tier resolution, fail-closed DEBUG, byte caps, span/id minters) ─
export {
CvdiagEmitter,
BYTE_CAP_BY_TIER,
QUEUE_CAP,
DEBUG_MAX_WALLCLOCK_MS,
DEBUG_MAX_EVENTS,
FLUSH_WINDOW_MS,
resolveEnvLabel,
mintTestId,
mintSpanId,
} from "../../../harness/src/cvdiag/emit.js";
export type {
CvdiagTier,
CvdiagPbWriter,
CvdiagEnv,
CvdiagEmitterOptions,
CvdiagEmitArgs,
} from "../../../harness/src/cvdiag/emit.js";
// ── Concrete writer-role PB writer (plain fetch; auth-with-password→Bearer) ──
export {
CvdiagFetchPbWriter,
createCvdiagFetchPbWriterFromEnv,
} from "../../../harness/src/cvdiag/pb-writer-fetch.js";
export type {
FetchLike,
CvdiagFetchPbWriterOptions,
} from "../../../harness/src/cvdiag/pb-writer-fetch.js";
@@ -0,0 +1,9 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["*.test.ts"],
environment: "node",
globals: false,
},
});