Adding an Event
Every tramai.* runtime event is declared exactly once in RuntimeEventCatalogue, referenced through the compile-time RuntimeEvents registry, and emitted through the engine's emission helper. The architecture guard scans both bytecode and source: any tramai.* string literal that does not go through RuntimeEvents.X fails the build — even if the literal already exists in the catalogue.
The Extension Points
All in tramai-core/src/main/kotlin/dev/tramai/core/observation/event/:
| File | Owns |
|---|---|
RuntimeEventCatalogue.kt | The object catalogue holding allEvents: List<RuntimeEventDefinition>; init fails fast on duplicate names, conflicting attribute types, required attributes outside the allowed set, and undeclared or duplicate metric mappings. event(name) errors on an unknown id. Also holds the dynamic DynamicAttributeNamespaces.WORKFLOW_CONTEXT namespace. |
RuntimeEventModel.kt | RuntimeAttributeKey<T>(name, valueType), RuntimeMetricDefinition, and RuntimeEventDefinition(name, domain, allowedAttributes, requiredAttributes, sensitivity, auditEligible, evidenceEligible, metricMapping, spanEligible, failurePolicy) |
RuntimeAttributes.kt | One RuntimeAttributeKey<T>("tramai…", T::class) per canonical key |
RuntimeMetrics.kt | Metric descriptors and the all aggregate |
RuntimeEvents.kt | The compile-time registry — for example val WORKER_HEARTBEAT = RuntimeEventCatalogue.event("tramai.worker.heartbeat") |
RuntimeEvent.kt | The typed builder: set rejects non-allowed attributes and wrong types, build enforces required attributes; plus `RuntimeEvent.of(definition |
Real names you can pattern-match against: tramai.worker.heartbeat, tramai.route.selected, tramai.circuit.opened, tramai.retry.scheduled, tramai.dlp.inspection_failed, tramai.token_budget.soft_limit_exceeded, tramai.parse.failure.
Files That Change
Work top to bottom — a new event must be consistent with every registry at once:
RuntimeAttributes.kt— add aRuntimeAttributeKeyif a new attribute is needed.RuntimeMetrics.kt— add aRuntimeMetricDefinitionif a new metric is needed.RuntimeEventCatalogue.kt— add theRuntimeEventDefinition(...)entry toallEvents.RuntimeEvents.kt— addval X = RuntimeEventCatalogue.event("tramai…").docs/reference/runtime-event-catalogue.md— regenerate, do not hand-edit (see below).- The emission call site in the owning module, through the module's emission helper.
The Generated Reference Document
docs/reference/runtime-event-catalogue.md is produced by RuntimeEventCatalogueRenderer and carries a "do not edit by hand" header. RuntimeEventCatalogueDocumentationTest fails with a "Regenerate it from RuntimeEventCatalogueRenderer" message when the committed document drifts from the catalogue. Regenerate it in the same change that adds the event.
Mandatory Contract Tests
| Test | Where | Pins |
|---|---|---|
RuntimeEventCatalogueTest | tramai-core/src/test/kotlin/dev/tramai/core/observation/event/ | Uniqueness, metadata, canonical types, determinism |
RuntimeEventCatalogueDocumentationTest | same package | Generated document matches the renderer |
RuntimeEventCatalogueArchitectureTest | tramai-observability/src/test/kotlin/dev/tramai/observability/ | Two fail-closed layers (below) |
RuntimeEventCatalogueVerifierRulesTest | same package | Mutation semantics of the verifier |
RuntimeEventCatalogueArchitectureTest enforces two independent scans:
- Bytecode scan — ASM9
visitLdcInsnover the classes oftramai-core,tramai-engine,tramai-orchestration, andtramai-observability. It skips onlydev/tramai/core/observation/event/, fails on any"tramai."LDC constant, and fails closed on zero classes scanned so a broken classpath can never silently pass. - Repo-wide source scan — every module's
src/main/**/*.kt, matched against"tramai\.[a-zA-Z0-9_.-]*". Only an exact allowlist of configuration-property literals passes.
Compatibility
- Event, attribute, and metric names are the long-term observability contract. External dashboards, alerts, and audit tooling depend on them; renaming is a breaking change.
- The catalogue init fails fast on duplicate names, conflicting attribute types, required attributes outside the allowed set, undeclared metric mappings, and duplicate metric names. A new event has to be consistent with all registries simultaneously.
- Emission honours the event's declared
failurePolicy: an emit failure must never break the invocation flow.
Verification
./gradlew :tramai-observability:test \
--tests '*RuntimeEventCatalogueArchitectureTest*' \
--tests '*RuntimeEventCatalogueVerifierRulesTest*'
./gradlew :tramai-core:test --tests '*RuntimeEventCatalogue*'
./gradlew verifyPr
The guard re-runs whenever a literal is added anywhere: tramai-observability/build.gradle.kts declares every module's production Kotlin sources as test inputs, so Gradle cannot skip it as up to date.
What Not To Change In This Pull Request
- The renderer and its documentation test. The reference document is generated.
- The architecture guard test and
RuntimeEventCatalogueVerifierRulesTest. They pin mutation semantics; never weaken them. - Existing event names or attribute types.
config/quality/0.6.0-baseline.json.
Common Mistakes
| Symptom | Cause |
|---|---|
| Documentation test fails | docs/reference/runtime-event-catalogue.md was hand-edited instead of regenerated |
RuntimeEventCatalogueArchitectureTest fails on a literal | A "tramai.…" string was emitted directly instead of through RuntimeEvents.X — the guard flags even already-catalogued literals |
| Catalogue init throws | A required attribute was not added to allowedAttributes, or a metric mapping was left undeclared |
| Guard passes locally but fails in CI | A module was added to the build without adding its production sources as guard inputs |
