Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.

Return structured errors and validate legacy append (#9, #10) - #21

Merged
frahlg merged 3 commits into
mainfrom
claude/api-hardening
Jul 21, 2026
Merged

frahlg merged 3 commits into
mainfrom
claude/api-hardening

Conversation

@frahlg

@frahlg frahlg commented Jul 21, 2026 •

Copy link
Copy Markdown
Member

Summary

  • return Error::InvalidArgument from public aggregate and rollup APIs instead of panicking on bad input
  • leave aggregate state unchanged when an out-of-order push fails
  • reject inverted point intervals on both transaction and catalog-less append paths
  • keep the catalog-less append contract explicit for old telemetry callers

Roadmap

This hardens M0 semantics and makes the M1 catalog boundary clear: transactions enforce catalog links, while the old append path enforces only point rules that do not need catalog state.

Checks

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-targets --all-features (70 library tests plus property and bench targets)

Closes #9
Closes #10

claude added 2 commits July 21, 2026 09:24
FixedGaugeRollup::build asserted that its resolution is positive and its
maximum gap non-negative, and Database::rollup_gauge forwarded
caller-supplied values straight into it, so a resolution of zero from a
library user aborted the whole process. The same reachability applied to
GaugeAggregate/CounterAggregate push and merge, which asserted timestamp
ordering on runtime data, and to GaugeBucket::merge_exact, which
asserted bucket adjacency.

A new Error::InvalidArgument(&'static str) variant now carries these
precondition failures: build, push, merge, and merge_exact return
Result, rollup_gauge propagates it, and a failed push provably leaves
the aggregate state untouched. CalendarGaugeRollup::build's existing
negative-gap check moves from InvalidConfig to the same variant so both
builders reject the same mistake the same way; InvalidConfig remains for
durable handle/policy settings. The debug_assert in
GaugeBucket::add_covered_interval stays: it is a private helper whose
callers iterate strictly increasing bucket edges, so no caller input can
reach it once build has validated its arguments.

Fixes #9

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCz23P2PXpQ7QAiVtCM5so
Database::append wrote FRAME_KIND_LEGACY_POINTS frames with zero
validation while Transaction commits run the catalog's validate_points,
so an interval that ends before it starts could only be rejected on one
of the two write paths, and Store::append inherited the hole.

The catalog-independent interval check now lives in a shared
validate_point_intervals helper that validate_points calls first and
append runs before encoding anything, so both writers reject an
inverted interval with the identical InvalidModel error and a rejected
batch never reaches the log.

Series existence and run provenance deliberately stay commit-only:
append is the documented catalog-less fast path — the benches, the
property tests, and the database round-trip tests all feed it raw
telemetry with undefined series and unresolvable run ids, and no
catalog ever exists on that path to validate against. Both append doc
comments now state that contract. Recovery of already-written legacy
frames stays lenient so files produced before this change still open.

Fixes #10

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCz23P2PXpQ7QAiVtCM5so
@frahlg
frahlg merged commit 3a9eef9 into main Jul 21, 2026
2 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Legacy append() path bypasses all point validation that commit() enforces Public API panics on bad input instead of returning Err

2 participants