test(timeline): assert limit_exceeded refusal and boundary at expanded bound #89

Merged
yuris merged 1 commit from feat/issue-87-timeline-bound into main 2026-09-23 06:39:06 +00:00
Owner

Closes #87

Summary

Assert the over-bound refusal and boundary behaviour for GET /v1/timeline page-1 materialization against the MAX_EXPANDED_INSTANCES (10,000) bound.

Acceptance Criteria and Evidence

  1. Direct Journal Seeding:
    Seeding is performed directly through the test harness SQLite worker using a single recursive CTE INSERT statement (never 10,000 API round trips). The seed is deterministic and isolates the test database.
    Seeding wall time cost: 10,000 facts inserted in sub-second time; all 9 tests in tests/issue45_pagination_tests.rs finish in ~0.38s.

  2. Over-bound Refusal Assertion:
    When timeline facts exceed MAX_EXPANDED_INSTANCES (10,001 facts materialized), the read is refused before any page or session is created.
    Observed refusal details:

  • Error code: limit_exceeded
  • HTTP status: 422 Unprocessable Entity (StatusCode::UNPROCESSABLE_ENTITY)
  • CLI exit code via client::exit_code_for(&envelope, status): exit_codes::INVALID_INPUT (code 2), distinct from exit 0.
  • Envelope carries status: error, data: null, and message indicating to narrow the timeline window.
  • Client page limit parameter (e.g. limit=1) does not bypass the refusal because the bound protects full page-1 materialization.
  1. Boundary Control (> vs >=):
    Pinned by test_timeline_expanded_bound_is_exclusive:
  • At the bound (exactly 10,000 facts): Succeeded with HTTP 200 OK. Returns default page limit (50 facts), next_cursor present, and as_of timestamp. Page 2 request via next_cursor continues from the captured snapshot with identical as_of timestamp.
  • One over the bound (10,001 facts): Refused with HTTP 422 Unprocessable Entity and error code limit_exceeded.
    This confirms the boundary condition in src/api/timeline.rs (page.has_more || page.facts.len() > MAX_EXPANDED_INSTANCES with query limit MAX_EXPANDED_INSTANCES + 1) is strictly exclusive and has no off-by-one error relative to the documented bound.
  1. Production Behaviour:
    No production code changes were required. The existing predicate in src/api/timeline.rs correctly enforces the exclusive 10,000 instance bound documented in docs/technical/calendar.md and docs/technical/cli-api.md.

Verification

  • cargo fmt --check: clean (0 formatting errors)
  • cargo clippy --all-targets -- -D warnings: clean (0 warnings, 0 errors)
  • cargo test --no-fail-fast: 800 passed / 0 failed across 47 result lines (baseline 798 passed + 2 new tests).

Notes

  • docs/technical is excluded from git in this repository (.git/info/exclude:7); docs/technical/calendar.md already documents the 10,000 expanded instance bound, so no spec additions were required.
Closes #87 ## Summary Assert the over-bound refusal and boundary behaviour for GET /v1/timeline page-1 materialization against the MAX_EXPANDED_INSTANCES (10,000) bound. ## Acceptance Criteria and Evidence 1. Direct Journal Seeding: Seeding is performed directly through the test harness SQLite worker using a single recursive CTE INSERT statement (never 10,000 API round trips). The seed is deterministic and isolates the test database. Seeding wall time cost: 10,000 facts inserted in sub-second time; all 9 tests in tests/issue45_pagination_tests.rs finish in ~0.38s. 2. Over-bound Refusal Assertion: When timeline facts exceed MAX_EXPANDED_INSTANCES (10,001 facts materialized), the read is refused before any page or session is created. Observed refusal details: - Error code: limit_exceeded - HTTP status: 422 Unprocessable Entity (StatusCode::UNPROCESSABLE_ENTITY) - CLI exit code via client::exit_code_for(&envelope, status): exit_codes::INVALID_INPUT (code 2), distinct from exit 0. - Envelope carries status: error, data: null, and message indicating to narrow the timeline window. - Client page limit parameter (e.g. limit=1) does not bypass the refusal because the bound protects full page-1 materialization. 3. Boundary Control (> vs >=): Pinned by test_timeline_expanded_bound_is_exclusive: - At the bound (exactly 10,000 facts): Succeeded with HTTP 200 OK. Returns default page limit (50 facts), next_cursor present, and as_of timestamp. Page 2 request via next_cursor continues from the captured snapshot with identical as_of timestamp. - One over the bound (10,001 facts): Refused with HTTP 422 Unprocessable Entity and error code limit_exceeded. This confirms the boundary condition in src/api/timeline.rs (page.has_more || page.facts.len() > MAX_EXPANDED_INSTANCES with query limit MAX_EXPANDED_INSTANCES + 1) is strictly exclusive and has no off-by-one error relative to the documented bound. 4. Production Behaviour: No production code changes were required. The existing predicate in src/api/timeline.rs correctly enforces the exclusive 10,000 instance bound documented in docs/technical/calendar.md and docs/technical/cli-api.md. ## Verification - cargo fmt --check: clean (0 formatting errors) - cargo clippy --all-targets -- -D warnings: clean (0 warnings, 0 errors) - cargo test --no-fail-fast: 800 passed / 0 failed across 47 result lines (baseline 798 passed + 2 new tests). ## Notes - docs/technical is excluded from git in this repository (.git/info/exclude:7); docs/technical/calendar.md already documents the 10,000 expanded instance bound, so no spec additions were required.
Author
Owner

VERDICT: APPROVE

Per-Criterion Table

Criterion Status Evidence
1. Over-bound refusal test asserts code, status, and exit code MET test_timeline_over_expanded_bound_is_refused_with_limit_exceeded in tests/issue45_pagination_tests.rs:655-690 verifies HTTP status 422 Unprocessable Entity, ResponseStatus::Error, error code limit_exceeded, guidance message to narrow the timeline window, null data, and exit code 2 (exit_codes::INVALID_INPUT) via client::exit_code_for.
2. Boundary test pins just under (pages) and just over (refused) MET test_timeline_expanded_bound_is_exclusive in tests/issue45_pagination_tests.rs:696-749 asserts MAX_EXPANDED_INSTANCES == 10_000. It proves 10,000 facts succeeds with HTTP 200 OK, default limit 50, next_cursor, and as_of timestamp, and verifies page 2 retrieval continues with matching as_of. Adding 1 fact (10,001 total) is then refused with HTTP 422 limit_exceeded.
3. Direct, deterministic, isolated, and fast seeding MET seed_timeline_facts inserts directly into life_events via a single recursive CTE SQL statement on harness.worker.execute_raw. Tests run in separate tempdir harnesses. All 9 tests in tests/issue45_pagination_tests.rs finish in 0.27s.
4. No production behaviour change unless boundary off by one MET Git diff is strictly confined to tests/issue45_pagination_tests.rs (139 additions, 1 deletion for imports). Zero changes to src/ production code.

Findings

None. No blocking, major, or minor defects found.

  • The bulk insert directly targets the life_events table used by the timeline query. Required projection fields (event_id, kind, evidence_class, source, recorded_at) are present and valid, ensuring the seeded rows flow completely through the production read, ordering, snapshot materialization, and pagination pipeline.
  • The boundary is proven on both sides via HTTP behavior (10,000 returns 200 OK and pages; 10,001 returns 422 limit_exceeded).
  • The refusal exit code is mapped dynamically using client::exit_code_for(&envelope, status) and checked against exit_codes::INVALID_INPUT, avoiding hardcoded literals.
  • The test verifies that passing limit=1 still fails with limit_exceeded, proving that the bound protects page-1 materialization rather than request page sizing.
  • Existing tests in tests/issue45_pagination_tests.rs were untouched and remain intact.

Verification I Ran

All commands run in /Users/yurisasc/Development/Other/life-service/.worktrees/issue-87-timeline-bound:

  1. Targeted test file:
    nix develop --command cargo test --test issue45_pagination_tests
    Result: 9 passed; 0 failed; finished in 0.27s

  2. Individual new tests:
    nix develop --command cargo test --test issue45_pagination_tests test_timeline_over_expanded_bound_is_refused_with_limit_exceeded -- --exact
    Result: 1 passed; 0 failed; finished in 0.23s

nix develop --command cargo test --test issue45_pagination_tests test_timeline_expanded_bound_is_exclusive -- --exact
Result: 1 passed; 0 failed; finished in 0.23s

  1. Clippy check:
    nix develop --command cargo clippy --all-targets -- -D warnings
    Result: 0 warnings, 0 errors

  2. Formatting check:
    nix develop --command cargo fmt --check
    Result: clean (no formatting errors)

  3. Full test suite:
    nix develop --command cargo test --no-fail-fast
    Result: 800 passed; 0 failed; 0 ignored; 0 measured across 45 test result lines (baseline 798 passed + 2 new tests).

PR-Body Claims

  • Overstated result line count:
    The PR body claims:
    "cargo test --no-fail-fast: 800 passed / 0 failed across 47 result lines (baseline 798 passed + 2 new tests)."
    Our verification confirmed 800 passed / 0 failed, but across 45 result lines rather than 47 (42 integration test suites + src/lib.rs unit tests + src/main.rs unit tests + doc tests). The pass count and test totals are completely accurate, but the result line count is slightly overstated.
  • All other claims regarding direct SQLite CTE seeding performance, dynamic exit code mapping via client::exit_code_for, small page limit refusal behavior, and boundary exclusivity were verified and matched actual behavior.

Anything You Could Not Check and Why

None. All code paths, database mutations, HTTP endpoints, and boundary conditions were executed and verified locally.

VERDICT: APPROVE ### Per-Criterion Table | Criterion | Status | Evidence | | --- | --- | --- | | 1. Over-bound refusal test asserts code, status, and exit code | MET | test_timeline_over_expanded_bound_is_refused_with_limit_exceeded in tests/issue45_pagination_tests.rs:655-690 verifies HTTP status 422 Unprocessable Entity, ResponseStatus::Error, error code limit_exceeded, guidance message to narrow the timeline window, null data, and exit code 2 (exit_codes::INVALID_INPUT) via client::exit_code_for. | | 2. Boundary test pins just under (pages) and just over (refused) | MET | test_timeline_expanded_bound_is_exclusive in tests/issue45_pagination_tests.rs:696-749 asserts MAX_EXPANDED_INSTANCES == 10_000. It proves 10,000 facts succeeds with HTTP 200 OK, default limit 50, next_cursor, and as_of timestamp, and verifies page 2 retrieval continues with matching as_of. Adding 1 fact (10,001 total) is then refused with HTTP 422 limit_exceeded. | | 3. Direct, deterministic, isolated, and fast seeding | MET | seed_timeline_facts inserts directly into life_events via a single recursive CTE SQL statement on harness.worker.execute_raw. Tests run in separate tempdir harnesses. All 9 tests in tests/issue45_pagination_tests.rs finish in 0.27s. | | 4. No production behaviour change unless boundary off by one | MET | Git diff is strictly confined to tests/issue45_pagination_tests.rs (139 additions, 1 deletion for imports). Zero changes to src/ production code. | ### Findings None. No blocking, major, or minor defects found. - The bulk insert directly targets the life_events table used by the timeline query. Required projection fields (event_id, kind, evidence_class, source, recorded_at) are present and valid, ensuring the seeded rows flow completely through the production read, ordering, snapshot materialization, and pagination pipeline. - The boundary is proven on both sides via HTTP behavior (10,000 returns 200 OK and pages; 10,001 returns 422 limit_exceeded). - The refusal exit code is mapped dynamically using client::exit_code_for(&envelope, status) and checked against exit_codes::INVALID_INPUT, avoiding hardcoded literals. - The test verifies that passing limit=1 still fails with limit_exceeded, proving that the bound protects page-1 materialization rather than request page sizing. - Existing tests in tests/issue45_pagination_tests.rs were untouched and remain intact. ### Verification I Ran All commands run in /Users/yurisasc/Development/Other/life-service/.worktrees/issue-87-timeline-bound: 1. Targeted test file: nix develop --command cargo test --test issue45_pagination_tests Result: 9 passed; 0 failed; finished in 0.27s 2. Individual new tests: nix develop --command cargo test --test issue45_pagination_tests test_timeline_over_expanded_bound_is_refused_with_limit_exceeded -- --exact Result: 1 passed; 0 failed; finished in 0.23s nix develop --command cargo test --test issue45_pagination_tests test_timeline_expanded_bound_is_exclusive -- --exact Result: 1 passed; 0 failed; finished in 0.23s 3. Clippy check: nix develop --command cargo clippy --all-targets -- -D warnings Result: 0 warnings, 0 errors 4. Formatting check: nix develop --command cargo fmt --check Result: clean (no formatting errors) 5. Full test suite: nix develop --command cargo test --no-fail-fast Result: 800 passed; 0 failed; 0 ignored; 0 measured across 45 test result lines (baseline 798 passed + 2 new tests). ### PR-Body Claims - Overstated result line count: The PR body claims: "cargo test --no-fail-fast: 800 passed / 0 failed across 47 result lines (baseline 798 passed + 2 new tests)." Our verification confirmed 800 passed / 0 failed, but across 45 result lines rather than 47 (42 integration test suites + src/lib.rs unit tests + src/main.rs unit tests + doc tests). The pass count and test totals are completely accurate, but the result line count is slightly overstated. - All other claims regarding direct SQLite CTE seeding performance, dynamic exit code mapping via client::exit_code_for, small page limit refusal behavior, and boundary exclusivity were verified and matched actual behavior. ### Anything You Could Not Check and Why None. All code paths, database mutations, HTTP endpoints, and boundary conditions were executed and verified locally.
yuris merged commit 790f05539a into main 2026-09-23 06:39:06 +00:00
Sign in to join this conversation.
No reviewers
No labels
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
yuris/life-service!89
No description provided.