feat(ahti-emitter): proto sync, Permanent classification, outcome health, constructor#109
feat(ahti-emitter): proto sync, Permanent classification, outcome health, constructor#109dimashev wants to merge 1 commit into
Conversation
…lth, constructor Polish pass on polku-ahti-emitter so it's safe to wire under the resilience stack with the new Retriability classification from polku#108: * Re-vendor `proto/ahti_append.proto` from the sibling ahti repo. The vendored copy had drifted: `APPEND_ERROR_CODE_SCHEMA_REGISTRY_UNCLASSIFIED` moved from 14 to 15 to make room for `APPEND_ERROR_CODE_UNKNOWN_NAMESPACE`, and `AppendError` gained a `repeated string available_namespaces = 12` field. `build.rs` regenerated `src/proto/ahti.append.v1.rs` accordingly. * `scripts/check-ahti-proto-sync.sh` flags drift between the vendored copy and the sibling Ahti checkout. Skips cleanly when the sibling is absent (CI containers); honors `AHTI_PROTO_SOURCE` for non-sibling layouts. * Map permanent gRPC statuses (`InvalidArgument`, `FailedPrecondition`, `OutOfRange`, `Unauthenticated`, `PermissionDenied`, `NotFound`, `AlreadyExists`, `Unimplemented`, `Aborted`) to `PluginError::Permanent` so `RetryEmitter` fails fast and `CircuitBreakerEmitter` treats them as neutral. Transient codes still map to `PluginError::Send` for retry. * Outcome-based `health()`: track most-recent success/failure as epoch-millis atomics shared across `Clone`s; report `Unhealthy` after the most recent Ahti append failed, `Healthy` otherwise (including the pre-emit state). * `AhtiEmitterConfig::for_occurrence(...)` constructor for the common case (Occurrence kind, localhost, Observed evidence, Short retention). All nine fields are still public for overrides. * Document the Occurrence-only boundary in `docs/ahti-emitter.md`: product records (entity_version, relationship_claim, decision, transition, etc.) are written by Vartio's Elixir core via `vartio_ahti_client`, not by Rust adapters. Adding a non-Occurrence kind here would mean a Rust adapter has drifted into product judgment — fix is to emit the underlying signal as an Occurrence and let Vartio interpret it. Tests: 24 lib tests pass (3 new for retriability classification, 4 new for outcome health, 3 new for `for_occurrence`); `cargo test --all` green. Pre-existing `clippy::manual_checked_ops` warning in `gateway/src/middleware/token_bucket.rs:41` fires on Rust 1.95 but not on CI's pinned 1.85 — unchanged by this PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review + fixesStrong PR — the permanent-code classification is the contract that makes #108 useful for Ahti, the exhaustive 1.
|
yairfalse
left a comment
There was a problem hiding this comment.
Branch feat/ahti-emitter-polish
- Comment posted: #issuecomment-4588161369
- Committed 5d15b8e with 2 fixes:
a. Code::Aborted → retriable (gRPC says retry at higher level; atomic rollback still surfaces as FailedPrecondition = permanent)
b. health() → single last-writer-wins AtomicU8, removing wall-clock skew + the same-ms masking bug; dropped now_ms()/spin-loop; added health_failure_after_success_is_unhealthy - ✅ fmt + clippy -D warnings + tests (28 passed)
Summary
Polish pass on
polku-ahti-emitterso it slots cleanly under the newRetriabilityclassification merged in #108. All bundled into one PR so the proto sync + classification mapping + tests land atomically — none of these are useful without the others.Changes
proto/ahti_append.protofrom sibling Ahti. The copy was stale:APPEND_ERROR_CODE_SCHEMA_REGISTRY_UNCLASSIFIEDshifted from14→15to make room forAPPEND_ERROR_CODE_UNKNOWN_NAMESPACE = 14, andAppendErrorgained arepeated string available_namespaces = 12field.build.rsregeneratedsrc/proto/ahti.append.v1.rs.scripts/check-ahti-proto-sync.sh— flags drift vs. the sibling ahti checkout. Skips cleanly when sibling absent (CI containers). HonorsAHTI_PROTO_SOURCEfor non-sibling layouts. Safe in a pre-commit hook.error.rs::into_plugin_error. Permanent gRPC statuses (InvalidArgument,FailedPrecondition,OutOfRange,Unauthenticated,PermissionDenied,NotFound,AlreadyExists,Unimplemented,Aborted) →PluginError::Permanent. Transient/Unknown (Unavailable,DeadlineExceeded,ResourceExhausted,Internal,Unknown,Cancelled,DataLoss) →PluginError::Send. This is the contract that makes feat(core,gateway): Retriability classification + RetryEmitter fail-fast #108 useful for Ahti — schema rejections and bad-token cases now fail fast at retry AND are neutral at the circuit breaker, while real outages still retry and trip the breaker.health(). Tracks most-recent success/failure as epoch-millisAtomicI64s shared across clones.Unhealthywhen the most recent observed batch failed;Healthyotherwise (including pre-first-emit so we don't flap on startup).AhtiEmitterConfig::for_occurrence(...)constructor — the common case in one call:Occurrencekind,http://localhost:50051,Observedevidence,Shortretention. All nine fields stay public for field-update overrides.docs/ahti-emitter.md. Product records (entity_version,relationship_claim,decision,transition, etc.) are written by Vartio's Elixir core viavartio_ahti_client, never through this crate. If a Rust adapter "needs" a non-Occurrence kind it has drifted into product judgment; the fix is to emit the underlying signal as an Occurrence and let Vartio interpret it. References Vartio invariants Add gRPC and Webhook emitters #5, Add Kubernetes integration via Seppo SDK #6, feat(gateway): Channel capacity tuning and Hub LockFreeBuffer wiring #11–13.Test coverage
error::tests::rejected_status_classification_covers_each_permanent_code— exhaustive overtonic::Codeerror::tests::rejected_status_classifies_retriability— end-to-end retriability forFailedPreconditionvsUnavailabletests::health_reports_healthy_with_no_outcomes_yet/health_reports_unhealthy_after_failure/health_recovers_after_subsequent_success/health_clones_share_outcome_stateconfig::tests::for_occurrence_builds_validatable_config/for_occurrence_overrides_apply_after_construction/for_occurrence_still_rejects_reserved_namespacecargo test -p polku-ahti-emitter: 24 passed, 0 failed.cargo test --allgreen workspace-wide.Pre-existing warning, unchanged
clippy::manual_checked_opsfires ongateway/src/middleware/token_bucket.rs:41under Rust 1.95 but not on CI's pinned 1.85. Same warning on main, unrelated to this PR. Verified by stashing the branch diff and re-running locally.Test plan
cargo fmt --all --checkcargo clippy -p polku-ahti-emitter --all-targets -- -D warningscargo test -p polku-ahti-emitter(24 unit + 3 ignored live-Ahti)cargo test --allscripts/check-ahti-proto-sync.shreturns 0 with sibling, skips withAHTI_PROTO_SOURCE=/dev/null, exits 1 on synthetic driftfor_occurrence(\"vartio-otel\", \"vartio.otel.span.detected.v1\", \"1\", token)in the planned live integration test🤖 Generated with Claude Code