Boundary consistency, proof freshness, OpenAPI validation & rate-limit enforcement (#345–#348) - #445
Merged
maugauwi-hash merged 4 commits intoAug 31, 2026
Conversation
floors.rs and caps.rs configured allocation bounds independently, so a floor set above its matching cap produced an unsatisfiable range that nothing rejected. - Add shared contracts/ttl_vault/src/range_check.rs with RangeError::FloorExceedsCap and ensure_floor_within_cap(floor, cap). - set_floor / set_cap now cross-check the other bound already stored for the same beneficiary and panic with the new ContractError::FloorExceedsCap when floor > cap. The check runs on every set, so post-initial-set updates are validated too. - Wire floors and caps into the crate module tree so their tests run. - Tests: valid range, floor-exceeds-cap rejection, and an update-after-initial-set regression test in both modules. - Also import testutils::Ledger in passkey_cap_tests.rs (it used env.ledger().with_mut without the trait in scope, which broke the ttl-vault test binary). Closes ethos-protocol#345
credential_anchoring anchored off-chain credential hashes on-chain but accepted anchoring proofs of any age, allowing stale or superseded credential states to be anchored. - Add set_max_proof_age / max_proof_age_seconds config (DEFAULT_MAX_PROOF_AGE_SECONDS = 3600). - Add anchor_credential(env, credential_id, external_id, system, proof_timestamp): rejects proofs older than the configured window or dated in the future; proof_is_fresh exposes the check directly. - Document the requirement in docs/issues-32-38-39-40.md. - Tests: fresh proof anchors, expired proof rejected, future-dated proof rejected, default window applies when unconfigured. Closes ethos-protocol#346
docs/openapi.yaml existed but nothing kept it in sync with the handlers, so clients could not rely on it. - Add backend/src/schema_validation.rs: parses the bundled docs/openapi.yaml into an OpenApiSpec and provides openapi_validation_middleware. Declared paths enforce their method set (405 + Allow on mismatch); undeclared internal routes pass through. - scripts/check_openapi_drift.mjs + .github/workflows/openapi.yml: CI job that fails when a public /api/** route is served but not documented, and runs the middleware tests + openapi-spec-validator. - scripts/generate_ts_client.mjs generates clients/typescript/src/client.ts (typed fetch client, one method per operation); CI runs it with --check so a stale client fails the build. - Tests: valid request passes, method mismatch rejected, parameters: is not treated as an operation, undeclared path passes through, path params match a single segment. The middleware is wired into build_router in the following commit (shared backend/src/main.rs). Closes ethos-protocol#347
rate_limit.rs defined UserTier and TierLimit but was not wired into the crate and no middleware applied it, so handlers could bypass it. - Wire pub mod rate_limit into the crate. - Add UserTier::Unauthenticated: a default-deny tier (TierLimit::default_deny) for callers with no Authorization header. - check_and_record_enforced applies the default-deny limit to endpoints with no registered config, so no route is silently unlimited. - enforce_rate_limit Axum middleware is added as the outermost layer in build_router, wrapping every route. Rejections return 429 with a Retry-After header (RateLimitError::retry_after_secs). - Also wires the ethos-protocol#347 OpenAPI validation middleware into build_router (shared backend/src/main.rs). - Tests: Free/Pro/Enterprise each hit their configured limit, Unauthenticated hits the default-deny limit, Admin is never limited, unregistered endpoints stay enforced, 429 carries Retry-After, header tier resolution. Closes ethos-protocol#348
|
@iam-mercy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Summary
Resolves four issues across the
ttl_vaultcontract and the API server.#345 — Floors and caps boundary consistency
contracts/ttl_vault/src/range_check.rsmodule:RangeError::FloorExceedsCapand
ensure_floor_within_cap(floor, cap).set_floorandset_capnow cross-check the other bound already configuredfor the same beneficiary and panic with the new
ContractError::FloorExceedsCapwhenfloor > cap. The check runs on everyset, so an update made after the initial configuration is validated too.
floors.rs/caps.rsare now wired into the crate module tree(
pub mod floors; pub mod caps;) so their tests actually run.for the update-after-initial-set case, in both modules plus
range_check.#346 — Credential anchoring proof freshness
credential_anchoring.rsgainsset_max_proof_age/max_proof_age_seconds(default
DEFAULT_MAX_PROOF_AGE_SECONDS= 3600s) and a newanchor_credential(env, credential_id, external_id, system, proof_timestamp)entry point that rejects proofs older than the configured window or dated in
the future.
proof_is_freshis exposed for callers that want to pre-check.docs/issues-32-38-39-40.md.rejected, default window applies when unconfigured.
#347 — OpenAPI schema validation
backend/src/schema_validation.rs: parses the bundleddocs/openapi.yamland exposes
OpenApiSpec+openapi_validation_middleware, applied to therouter in
main.rs. Declared paths enforce their method set (405+Allowon mismatch); undeclared internal routes pass through.
scripts/check_openapi_drift.mjs+.github/workflows/openapi.yml: CI jobthat fails when a public
/api/**route is served but not documented, andruns the middleware unit tests +
openapi-spec-validator.scripts/generate_ts_client.mjsgeneratesclients/typescript/src/client.ts(typed
fetchclient, one method per operation); the CI job runs it with--checkso a stale client fails the build.parameters:block isnot mistaken for an operation, undeclared path passes through, path params
match a single segment.
#348 — Rate-limit enforcement on all routes
backend/src/rate_limit.rsis now wired into the crate. NewUserTier::Unauthenticateddefault-deny tier (strictTierLimit::default_deny)for callers with no
Authorizationheader; unregistered endpoints also fallback to default-deny via
check_and_record_enforced, so no handler can bypassthe limiter.
enforce_rate_limitAxum middleware is the outermost app layer inbuild_router, wrapping every route. Rejections return429 Too Many Requestswith aRetry-Afterheader (RateLimitError::retry_after_secs).hits the default-deny limit, Admin is never limited, unregistered endpoints
are still enforced,
429carriesRetry-After, and header-based tierresolution.
Notes
passkey_cap_tests.rsusedenv.ledger().with_mut(...)without importingtestutils::Ledger, so thettl-vaulttest binary did not compile. Added the trait import and switchedto
set_timestamp. Unrelated pre-existing warnings inslice_performance_tests.rs(
String::from_slicedeprecation) andcredential_lifecycle_tests.rsareleft as-is.
cargo test -p ethos-protocol-backend—rate_limit+schema_validationsuites pass (37 tests).
Closes #345
Closes #346
Closes #347
Closes #348