provider-usage: add errorClass, a machine-readable reason for a degraded entry - #8
Merged
Conversation
…ded entry Every degraded entry looks alike on this wire. A provider nobody configured and a provider whose credential broke this morning both arrive as an entry with an error string, and the only thing telling them apart is that string -- which is prose with no stability promise, and which consumers are explicitly told not to parse. The consequence is a state change with no observable: a provider that worked yesterday and failed today moves a count by one and produces no other signal. On the current host that is 3 genuinely failing providers hidden among 25 that are unconfigured and always will be. errorClass carries the reason as a stable name derived from the producer taxonomy rather than from the message text. Classes today: credential_absent, credential_unusable, credential_rejected, no_quota_reported, upstream_failed, decode_failed. It is a String rather than an enum deliberately. The class list will grow, and on an observability surface an unrecognised value must not become a parse failure -- that would make a provider vanish from the output at exactly the moment its state changed. Consumers render an unknown class as degraded-with-unknown-reason, and a test pins that an unknown value decodes intact. Additive and non-breaking: absent on healthy entries, omitted when unset, and a producer that never sets it serializes exactly as before, which is also pinned by a test. degraded() is unchanged for existing callers; degraded_with_class() is the constructor for producers that know the class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
Every degraded entry on this wire looks alike. A provider nobody configured and a provider whose credential broke this morning both arrive as an entry with an
errorstring, and the only thing separating them is that string — which is prose with no stability promise, and which the consumer contract explicitly tells consumers not to parse.So a state change has no observable. A provider that worked yesterday and failed today moves a count by one and produces no other signal. On the current host: 3 genuinely failing providers hidden among 25 that are unconfigured and always will be.
ck quotarenders exactly this today — "28 providers not connected" — and its filter is a presence check onusage, which is the right call against the wire as it stands. The gap is in the wire, not the renderer.What this adds
errorClass: Option<String>, camelCase on the wire, additive and skip-if-none.credential_absentcredential_unusablecredential_rejectedno_quota_reportedupstream_faileddecode_failedThe producer derives the value from its error enum, never from message text, so it cannot drift from the taxonomy it names.
Why
Stringand not an enumThis is deliberate, and it is the opposite of the type-first instinct.
The class list will grow. An enum makes an unrecognised value a parse failure, which on an observability surface means the entry disappears from the output at exactly the moment its state changed — the failure mode this field exists to fix, reintroduced one layer down. A string means a consumer meets a new class as data and prints it.
Consumers must render an unknown class as degraded-with-unknown-reason: never drop the entry, never fold it into an existing bucket.
an_unknown_class_decodes_rather_than_failingpins that.Compatibility
Additive and non-breaking:
an_entry_without_a_class_serializes_as_it_did_before_the_field_existed.degraded()is unchanged, so existing callers (including astrocyte'scapacity_mapfixture) compile untouched.degraded_with_class()is the new constructor for producers that know the class.Version bumped 0.3.0 → 0.4.0.
Producer side
The producer taxonomy was made honest before this field was proposed, in ai-provider-quota
400b032— around ten sites were reporting "credential present but broken" as absent, which would have filed broken credentials under nothing-to-do-here. That is the fail-open direction on an observability surface, so it had to be fixed first rather than after.error_class()there matches exhaustively over the error enum, so a new variant fails to compile rather than reaching consumers unclassified.Producer-minted vectors (one entry per class, generated by serializing the real types rather than hand-written JSON) follow in the producer repo, so the consumer renderer is tested against what the producer actually emits.
Verification
cargo fmt --checkclean,clippy -D warnings0, 10/10 tests pass — 4 of them new, covering absence, round-trip, unknown-value tolerance, and healthy-entry absence.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Add
errorClassto provider usage entries to give a stable, machine-readable reason for degraded entries. This makes real failures visible and distinct from unconfigured providers without breaking existing consumers.New Features
errorClass(string) besideerroron degraded entries; omitted on healthy/unset.degraded_with_class(...);degraded(...)unchanged.cortexkit-provider-usageto0.4.0.Migration
degraded_with_classwhen the class is known; otherwise continue withdegraded.Written for commit d20ec05. Summary will update on new commits.