Preserve typed provider failures for central admission and provenance #674

Merged
toasterson merged 6 commits from fix/anima-a1-provider-outcomes into main 2026-10-19 20:00:26 +00:00
Owner

Preserve typed provider failures/reset provenance through model boundaries so central admission can use exact account/model windows without polling providers.

Targets main. Supplies Q1 #795 and the compatibility floor for the stack. No PR merge, deployment, live recovery or canary.

Review fixes

Reset tests now reach the actual backoff branch: no Retry-After plus named5s reset waits at least5s before a second send; named2h reset with1s deadline rejects after one send. Configured identities match Anima's [A-Za-z0-9_.:/-]{1,128} grammar. Usage-limit rejection never retries locally; missing identity decodes as unknown. This PR also supplies the metadata-tolerant reader rollback floor required by #677/#678; unknown control events still fail closed.

Exact consumer contract

Model failure v1 (A1; Anima Q1/S3)

RuntimeJob.error and TurnRecord.error retain their existing Fault.code and
message. An optional model_failure field carries sanitized model-boundary
observations. It is omitted for legacy/non-model faults; missing values are
unknown, never zero, and this contract grants no authority to replay work.

{"code":"model_incomplete","message":"model_error: model returned HTTP 429; code=rate_limit_exceeded; type=rate_limit; request_id=req-123; attempts=3","model_failure":{"schema_version":1,"cause":"rate_limit","http_status":429,"retry_after_secs":0,"provider_code":"rate_limit_exceeded","provider_type":"rate_limit","provider_request_id":"req-123","attempts":3}}

Required metadata fields: schema_version:1 and cause. The cause vocabulary is
rate_limit, usage_limit, context_limit, output_limit, deadline, cancelled, transport,
provider_error, incomplete. Optional fields are http_status:u16,
retry_after_secs:u64, provider_code:string, provider_type:string,
provider_request_id:string, finish_reason:string, attempts:u32.

Provider identifiers and finish reasons allow ASCII alphanumeric, _, -, .
and at most 128 characters. No raw provider body or prompt enters metadata.
retry_after_secs is present only for numeric Retry-After in 0..=30. It preserves
the final rejection's header even at attempt three; it does not request another
retry. No header means absent metadata, despite the runtime's local 1s/2s default
backoff. Invalid, HTTP-date or >30s headers stop local retry and remain absent.

Only explicit HTTP 429 responses can retry, at most three sends. Cancellation
and the enclosing turn deadline still interrupt sends, reads and backoff. Partial
streams, transport ambiguity and tools are not retried. HTTP length finish maps
to output_limit; explicit context-refusal codes map to context_limit; missing
finish/DONE stays incomplete. A native finish reason never invents HTTP status,
request identity or a provider attempt count. Legacy model-boundary lifecycle
codes remain model_incomplete / model_cancelled so continuation reconciliation
and operator fences retain their semantics.

Consumers should first parse the optional typed metadata, retain their exact
legacy structured-error parser, and preserve unknown fields/versions as unknown.
A1 supplies classification for Q1's durable admission cooldown; per-call usage,
actual model revision and context/elision provenance are separate A3/S3 work.
Source evidence is analysis-anima-3w/synthesis.md E02/E04/E06, not a deployment
or historical provider-model attestation. Deployment and authorized canary pending.

Durable usage-limit windows (operator extension)

Rate/usage rejections also carry optional model_failure.limit:

{"schema_version":1,"kind":"tokens","reset_at_ms":1792567680000,"reset_source":{"kind":"header","name":"x-ratelimit-reset-tokens"},"identity":{"provider":"api.example.com","account":null,"model":"requested-model"}}

All five members are present. kind is requests|tokens|monthly_usage|null;
reset_at_ms is nullable absolute UTC Unix milliseconds, including long waits;
reset_source is nullable {kind:header|body,name:string}. identity has nullable
provider, account, model. Provider uses explicit ModelEndpoint.provider_identity
or endpoint DNS host, account only explicit opaque account_identity, model the
exact request binding. Configured identities are validated, never sanitized into
another value. No credential, token digest, default account or model alias is
substituted. An endpoint proxy host identifies that endpoint, not its hidden backend.

Supported sources: Retry-After integer seconds or HTTP-date; named
x-ratelimit-reset[-requests|-tokens|-project-requests|-project-tokens] in compound
durations (including ms), Unix epoch seconds/milliseconds or RFC3339; Anthropic
request/token/input-token/output-token reset RFC3339 headers. Header names retain
the selected canonical name from the HTTP library. Positive matching remaining
counts exclude unexhausted windows. Among exhausted or unknown windows and body
fields, select the latest valid reset conservatively, with its exact source/kind.
Body allowlist: /error/ or root reset_at_ms, reset_at, resets_at,
retry_after, retry_after_seconds, retry_after_ms; source name is its JSON
pointer. Only explicit limit_kind/limit_type, monthly_usage_limit code, or literal
monthly usage limit wording establish body kind. An unrelated named
request/token window is excluded when the body names a different limit kind.
Malformed/ambiguous values remain null. insufficient_quota yields usage_limit
but does not establish a monthly quota. Historical records remain unknown.

The legacy retry_after_secs remains independently bounded to numeric <=30.
Local sends retain the explicit-429 three-attempt policy. A known reset extends
backoff (never earlier than reset); if this exceeds 30s or remaining turn deadline,
return the original typed rejection immediately, rather than consuming the deadline.
Invalid/date/>30 Retry-After still stops local retry, while its parsed reset is
available to durable central admission. No rate/usage endpoint is queried.

Header format references: OpenAI rate limits,
Claude rate limits.

Review refinements: configured identities use [A-Za-z0-9_.:/-]{1,128}, matching Anima (no @). Missing limit identity defaults to all unknown. UsageLimit stops on the first rejection even without a reset; transient rate limits retain the three-send bound. ModelEndpoint uses deny_unknown_fields: rollback must also restore the older profile configuration before starting the older runtime.

Runtime/session readers in this compatibility release skip future model_observation/context_transform/turn_usage metadata when unsupported, while unknown control events still fail closed. This is the minimum rollback floor for the telemetry stack; binaries predating this floor require a pre-upgrade store/config snapshot, not an in-place rollback.

Validation

Fable Round2 approved reviewed head b79e50adb8d3. Current follow-up head: b2452e081e25604a5ce76da103075d7bfeef5b15.

The beyond-deadline case uses a20s named-header reset and10s deadline: ordinary1s retry spacing <10s deadline <20s reset <30s local cap. It asserts one send, typed requests limit, exact header provenance and immediate return before the deadline. This isolates the remaining-deadline check; the5s in-deadline reset still waits and retries successfully.

Current-head focused reset test:1 passed (r2-followup-a1.log). Final integrated #678 suite115 passed/1 existing real-backend ignored. Full exact-head Solstice CI remains a merge gate; local results do not substitute for it. No merge, force-push, deployment or canary.

Finished task target was reclaimed after verifying no open files;37.08GiB free at10:16Z. Peer target preserved.

Preserve typed provider failures/reset provenance through model boundaries so central admission can use exact account/model windows without polling providers. Targets main. Supplies Q1 #795 and the compatibility floor for the stack. No PR merge, deployment, live recovery or canary. ## Review fixes Reset tests now reach the actual backoff branch: no Retry-After plus named5s reset waits at least5s before a second send; named2h reset with1s deadline rejects after one send. Configured identities match Anima's `[A-Za-z0-9_.:/-]{1,128}` grammar. Usage-limit rejection never retries locally; missing identity decodes as unknown. This PR also supplies the metadata-tolerant reader rollback floor required by #677/#678; unknown control events still fail closed. ## Exact consumer contract # Model failure v1 (A1; Anima Q1/S3) `RuntimeJob.error` and `TurnRecord.error` retain their existing `Fault.code` and `message`. An optional `model_failure` field carries sanitized model-boundary observations. It is omitted for legacy/non-model faults; missing values are unknown, never zero, and this contract grants no authority to replay work. ```json {"code":"model_incomplete","message":"model_error: model returned HTTP 429; code=rate_limit_exceeded; type=rate_limit; request_id=req-123; attempts=3","model_failure":{"schema_version":1,"cause":"rate_limit","http_status":429,"retry_after_secs":0,"provider_code":"rate_limit_exceeded","provider_type":"rate_limit","provider_request_id":"req-123","attempts":3}} ``` Required metadata fields: `schema_version:1` and `cause`. The cause vocabulary is `rate_limit`, `usage_limit`, `context_limit`, `output_limit`, `deadline`, `cancelled`, `transport`, `provider_error`, `incomplete`. Optional fields are `http_status:u16`, `retry_after_secs:u64`, `provider_code:string`, `provider_type:string`, `provider_request_id:string`, `finish_reason:string`, `attempts:u32`. Provider identifiers and finish reasons allow ASCII alphanumeric, `_`, `-`, `.` and at most 128 characters. No raw provider body or prompt enters metadata. `retry_after_secs` is present only for numeric Retry-After in 0..=30. It preserves the final rejection's header even at attempt three; it does not request another retry. No header means absent metadata, despite the runtime's local 1s/2s default backoff. Invalid, HTTP-date or >30s headers stop local retry and remain absent. Only explicit HTTP 429 responses can retry, at most three sends. Cancellation and the enclosing turn deadline still interrupt sends, reads and backoff. Partial streams, transport ambiguity and tools are not retried. HTTP `length` finish maps to `output_limit`; explicit context-refusal codes map to `context_limit`; missing finish/DONE stays `incomplete`. A native finish reason never invents HTTP status, request identity or a provider attempt count. Legacy model-boundary lifecycle codes remain `model_incomplete` / `model_cancelled` so continuation reconciliation and operator fences retain their semantics. Consumers should first parse the optional typed metadata, retain their exact legacy structured-error parser, and preserve unknown fields/versions as unknown. A1 supplies classification for Q1's durable admission cooldown; per-call usage, actual model revision and context/elision provenance are separate A3/S3 work. Source evidence is `analysis-anima-3w/synthesis.md` E02/E04/E06, not a deployment or historical provider-model attestation. Deployment and authorized canary pending. ## Durable usage-limit windows (operator extension) Rate/usage rejections also carry optional `model_failure.limit`: ```json {"schema_version":1,"kind":"tokens","reset_at_ms":1792567680000,"reset_source":{"kind":"header","name":"x-ratelimit-reset-tokens"},"identity":{"provider":"api.example.com","account":null,"model":"requested-model"}} ``` All five members are present. `kind` is `requests|tokens|monthly_usage|null`; `reset_at_ms` is nullable absolute UTC Unix milliseconds, including long waits; `reset_source` is nullable `{kind:header|body,name:string}`. `identity` has nullable `provider`, `account`, `model`. Provider uses explicit `ModelEndpoint.provider_identity` or endpoint DNS host, account only explicit opaque `account_identity`, model the exact request binding. Configured identities are validated, never sanitized into another value. No credential, token digest, default account or model alias is substituted. An endpoint proxy host identifies that endpoint, not its hidden backend. Supported sources: Retry-After integer seconds or HTTP-date; named x-ratelimit-reset[-requests|-tokens|-project-requests|-project-tokens] in compound durations (including ms), Unix epoch seconds/milliseconds or RFC3339; Anthropic request/token/input-token/output-token reset RFC3339 headers. Header names retain the selected canonical name from the HTTP library. Positive matching remaining counts exclude unexhausted windows. Among exhausted or unknown windows and body fields, select the latest valid reset conservatively, with its exact source/kind. Body allowlist: `/error/` or root `reset_at_ms`, `reset_at`, `resets_at`, `retry_after`, `retry_after_seconds`, `retry_after_ms`; source name is its JSON pointer. Only explicit `limit_kind`/`limit_type`, `monthly_usage_limit` code, or literal `monthly usage limit` wording establish body kind. An unrelated named request/token window is excluded when the body names a different limit kind. Malformed/ambiguous values remain null. `insufficient_quota` yields `usage_limit` but does not establish a monthly quota. Historical records remain unknown. The legacy `retry_after_secs` remains independently bounded to numeric <=30. Local sends retain the explicit-429 three-attempt policy. A known reset extends backoff (never earlier than reset); if this exceeds 30s or remaining turn deadline, return the original typed rejection immediately, rather than consuming the deadline. Invalid/date/>30 Retry-After still stops local retry, while its parsed reset is available to durable central admission. No rate/usage endpoint is queried. Header format references: [OpenAI rate limits](https://developers.openai.com/api/docs/guides/rate-limits), [Claude rate limits](https://platform.claude.com/docs/en/api/rate-limits). Review refinements: configured identities use `[A-Za-z0-9_.:/-]{1,128}`, matching Anima (no @). Missing limit identity defaults to all unknown. UsageLimit stops on the first rejection even without a reset; transient rate limits retain the three-send bound. ModelEndpoint uses deny_unknown_fields: rollback must also restore the older profile configuration before starting the older runtime. Runtime/session readers in this compatibility release skip future model_observation/context_transform/turn_usage metadata when unsupported, while unknown control events still fail closed. This is the minimum rollback floor for the telemetry stack; binaries predating this floor require a pre-upgrade store/config snapshot, not an in-place rollback. ## Validation Fable Round2 approved reviewed head `b79e50adb8d3`. Current follow-up head: `b2452e081e25604a5ce76da103075d7bfeef5b15`. The beyond-deadline case uses a20s named-header reset and10s deadline: ordinary1s retry spacing <10s deadline <20s reset <30s local cap. It asserts one send, typed requests limit, exact header provenance and immediate return before the deadline. This isolates the remaining-deadline check; the5s in-deadline reset still waits and retries successfully. Current-head focused reset test:1 passed (r2-followup-a1.log). Final integrated #678 suite115 passed/1 existing real-backend ignored. Full exact-head Solstice CI remains a merge gate; local results do not substitute for it. No merge, force-push, deployment or canary. Finished task target was reclaimed after verifying no open files;37.08GiB free at10:16Z. Peer target preserved.
Preserve typed model failure provenance at internal boundary
Some checks failed
CI / test-session-platform (pull_request) Successful in 38m1s
CI / platform-images-home (pull_request) Has been cancelled
CI / platform-images-core (pull_request) Has been cancelled
CI / platform-images-daemons (pull_request) Has been cancelled
CI / platform-images-fleet (pull_request) Has been cancelled
CI / platform-images-collect (pull_request) Has been cancelled
CI / docker-akh-transcripts (pull_request) Has been cancelled
CI / docker-seshd (pull_request) Has been cancelled
CI / check-seshat (pull_request) Has been cancelled
CI / check-akh-medu (pull_request) Has been cancelled
CI / publish-chart (pull_request) Has been cancelled
CI / test-native-receiver-isolation (pull_request) Has been cancelled
CI / docker-sokard (pull_request) Has been cancelled
CI / test-akh-medu (pull_request) Has been cancelled
CI / test-native-client-computer (pull_request) Successful in 6m28s
CI / test-sokar-sharing (pull_request) Successful in 2m3s
CI / test-sokar (pull_request) Successful in 15m20s
935bd735e1
Carry durable provider limit resets and exact call identity
Some checks failed
CI / test-session-platform (pull_request) Has been cancelled
CI / test-native-receiver-isolation (pull_request) Has been cancelled
CI / test-native-client-computer (pull_request) Has been cancelled
CI / test-sokar-sharing (pull_request) Has been cancelled
CI / test-sokar (pull_request) Has been cancelled
CI / check-seshat (pull_request) Has been cancelled
CI / check-akh-medu (pull_request) Has been cancelled
CI / test-akh-medu (pull_request) Has been cancelled
CI / docker-seshd (pull_request) Has been cancelled
CI / docker-sokard (pull_request) Has been cancelled
CI / docker-akh-transcripts (pull_request) Has been cancelled
CI / publish-chart (pull_request) Has been cancelled
CI / platform-images-home (pull_request) Has been cancelled
CI / platform-images-core (pull_request) Has been cancelled
CI / platform-images-daemons (pull_request) Has been cancelled
CI / platform-images-fleet (pull_request) Has been cancelled
CI / platform-images-collect (pull_request) Has been cancelled
2f670d5d88
Distinguish explicit monthly limits from unrelated rate windows
Some checks failed
CI / test-sokar (pull_request) Has been cancelled
CI / check-seshat (pull_request) Has been cancelled
CI / check-akh-medu (pull_request) Has been cancelled
CI / test-akh-medu (pull_request) Has been cancelled
CI / docker-seshd (pull_request) Has been cancelled
CI / docker-sokard (pull_request) Has been cancelled
CI / docker-akh-transcripts (pull_request) Has been cancelled
CI / publish-chart (pull_request) Has been cancelled
CI / platform-images-home (pull_request) Has been cancelled
CI / platform-images-core (pull_request) Has been cancelled
CI / platform-images-daemons (pull_request) Has been cancelled
CI / platform-images-fleet (pull_request) Has been cancelled
CI / platform-images-collect (pull_request) Has been cancelled
CI / test-session-platform (pull_request) Has been cancelled
CI / test-native-receiver-isolation (pull_request) Has been cancelled
CI / test-sokar-sharing (pull_request) Has been cancelled
CI / test-native-client-computer (pull_request) Has been cancelled
785ff07c52
Add rollback-safe reader floor for future model metadata
Some checks failed
CI / test-native-receiver-isolation (pull_request) Successful in 3m59s
CI / check-seshat (pull_request) Successful in 2m11s
CI / test-native-client-computer (pull_request) Successful in 5m55s
CI / test-sokar (pull_request) Successful in 8m6s
CI / publish-chart (pull_request) Successful in 45s
CI / check-akh-medu (pull_request) Successful in 6m52s
CI / docker-seshd (pull_request) Successful in 7m7s
CI / platform-images-home (pull_request) Has been cancelled
CI / platform-images-core (pull_request) Has been cancelled
CI / platform-images-daemons (pull_request) Has been cancelled
CI / platform-images-fleet (pull_request) Has been cancelled
CI / platform-images-collect (pull_request) Has been cancelled
CI / test-sokar-sharing (pull_request) Has been cancelled
CI / docker-akh-transcripts (pull_request) Has been cancelled
CI / test-akh-medu (pull_request) Has been cancelled
CI / docker-sokard (pull_request) Has been cancelled
CI / test-session-platform (pull_request) Has been cancelled
b79e50adb8
test(runtime): isolate reset beyond remaining deadline
All checks were successful
CI / test-native-receiver-isolation (pull_request) Successful in 3m27s
CI / check-seshat (pull_request) Successful in 2m8s
CI / test-native-client-computer (pull_request) Successful in 5m16s
CI / test-sokar-sharing (pull_request) Successful in 5m30s
CI / test-sokar (pull_request) Successful in 6m38s
CI / check-akh-medu (pull_request) Successful in 6m45s
CI / docker-seshd (pull_request) Successful in 8m4s
CI / docker-akh-transcripts (pull_request) Successful in 8m47s
CI / test-akh-medu (pull_request) Successful in 19m40s
CI / docker-sokard (pull_request) Successful in 18m27s
CI / publish-chart (pull_request) Successful in 15m28s
CI / test-session-platform (pull_request) Successful in 44m2s
CI / platform-images-home (pull_request) Successful in 47s
CI / platform-images-core (pull_request) Successful in 19m59s
CI / platform-images-daemons (pull_request) Successful in 36m52s
CI / platform-images-fleet (pull_request) Successful in 59s
CI / platform-images-collect (pull_request) Successful in 2m0s
b2452e081e
Sign in to join this conversation.
No reviewers
No labels
follow-up
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
toasterson/akh-medu!674
No description provided.