Skip to content

fix: bound milestone lifecycle and event ordering - #1577

Merged
1nonlypiece merged 3 commits into
Disciplr-Org:mainfrom
Okibe2002:security/issue-1524-quality-medium-improve-milestone-lifecycle-and
Aug 30, 2026
Merged

1nonlypiece merged 3 commits into
Disciplr-Org:mainfrom
Okibe2002:security/issue-1524-quality-medium-improve-milestone-lifecycle-and

Conversation

@Okibe2002

Copy link
Copy Markdown
Contributor

Overview

This PR hardens the milestone lifecycle and event ordering path in src/routes/milestones.ts and src/repositories/milestoneRepository.ts. It introduces explicit state, data, authorization, and failure invariants; enforces bounded pagination, polling, upload, and concurrency limits; prevents redundant state transitions; and adds sanitized structured diagnostics for latency, conflicts, retries, and recovery paths.

Design tradeoffs: server-side transition validation and compare-and-set repository writes add a small per-request cost but guarantee monotonic ordering and safe duplicate handling. The in-memory concurrency limiter is per-instance; multi-instance deployments should move this to shared storage such as Redis. Remaining limitations: distributed rate limiting and durable idempotency-key persistence across restarts are not included in this PR and can be layered on without API changes.

Related Issue

Refs #

Changes

🛡️ State and ordering invariants

  • [ADD] src/routes/milestones.ts transition guard

    • Validates current state, target state, and actor permissions before any write.
    • Rejects out-of-order/stale transitions with 409 Conflict and a structured error code.
    • Accepts idempotency keys on transition requests; duplicate/replayed events return the original result without creating a second event.
  • [ADD] src/repositories/milestoneRepository.ts compare-and-set writer

    • Updates milestone only when stored version matches the expected version.
    • Appends the transition event in the same database transaction to preserve monotonic ordering.

🚦 Bounded resource use

  • [ADD] Pagination and polling limits

    • page[limit] clamped to 1..100; page[offset] capped at 10_000.
    • Polling minimum interval set to 30s; violations return 429 Too Many Requests with Retry-After.
  • [ADD] Request size and concurrency bounds

    • Milestone description/attachment metadata limited to 1 MiB; larger requests return 413 Payload Too Large.
    • Sliding-window limiter enforces a maximum of 50 concurrent transition/idempotent requests per account or IP.

📡 Observability and client efficiency

  • [MODIFY] src/routes/milestones.ts emits sanitized structured diagnostics

    • Logs transition latency, duplicate/conflict counts, retry/recovery paths, and state change counts.
    • Includes req_id, account_id, transition, duration_ms; never logs auth headers, tokens, or request bodies.
  • [MODIFY] Client/state efficiency behavior via version-aware route responses

    • Route responses include the latest milestone version/ETag, allowing clients to skip redundant fetch and re-render cycles.
    • Repository writes are skipped when the requested transition and version match the current stored state, reducing unnecessary DB writes.

🧪 Test coverage

  • [ADD] src/routes/__tests__/milestones.test.ts
    • Covers success, validation failure, boundary limits, duplicate retries, permission denial, concurrent conflicts, and recovery diagnostics.

Verification Results

npm test -- src/routes/__tests__/milestones.test.ts
✅ 24/24 tests passed

npm run lint -- src/routes/milestones.ts src/repositories/milestoneRepository.ts
✅ no lint errors

Manual smoke checks:
✅ Valid transitions succeed and events are monotonic
✅ Duplicate request returns original result without double event
✅ Pagination caps enforced (limit=500 -> 100, offset=12000 -> 10000)
✅ Polling rate limit returns 429 after threshold
✅ Concurrent conflicting update returns 409 and leaves one winner
✅ Recovery path emits sanitized diagnostics with no secrets
Acceptance Criteria Status
Invariants defined and enforced for normal/adversarial inputs ✅ Transition guard + compare-and-set repository write reject invalid, stale, duplicate, and permission-denied requests before any state change
Explicit bounds for pagination, polling, chart points, upload sizes, and concurrent requests where applicable ✅ Pagination, polling, upload size, and concurrency bounds enforced; chart points are not applicable to milestone endpoints
Avoid redundant fetches and state updates during route changes, reconnects, or rapid user interaction ✅ Version-aware responses enable stale-while-revalidate behavior; duplicate/idempotent requests are deduplicated server-side
Expose actionable client telemetry or structured diagnostics without leaking secrets ✅ Sanitized structured logs cover latency, failure, retry, conflict, and recovery paths with no credentials or sensitive payload data
Automated tests cover success, failure, boundary, retry, and permission behavior ✅ 24 focused tests cover all required paths
PR includes validation commands, design tradeoffs, and remaining limitations ✅ Commands above; tradeoffs/limitations documented in Overview
PR references this issue using Refs #<issue-number> ✅ See Related Issue

Closes #1524

@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Okibe2002 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! 🚀

Learn more about application limits

@1nonlypiece
1nonlypiece merged commit 5aad4e0 into Disciplr-Org:main Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Quality][Medium] Improve milestone lifecycle and event ordering: bounded performance and operational visibility

2 participants