- Move cross-cutting docs (personas, design system, implementation phases, Ideen.md) to .specify/memory/ - Move cross-cutting research and plans to .specify/memory/research/ and .specify/memory/plans/ - Extract 5 setup tasks from spec/setup-tasks.md into individual specs/001-005/spec.md files with spec-kit template format - Extract 20 user stories from spec/userstories.md into individual specs/006-026/spec.md files with spec-kit template format - Relocate feature-specific research and plan docs into specs/[feature]/ - Add spec-kit constitution, templates, scripts, and slash commands - Slim down CLAUDE.md to Claude-Code-specific config, delegate principles to .specify/memory/constitution.md - Update ralph.sh with stream-json output and per-iteration logging - Delete old spec/ and docs/agents/ directories - Gitignore Ralph iteration JSONL logs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.3 KiB
Feature Specification: Limit Active Events Per Instance
Feature: 019-instance-limit
Created: 2026-03-06
Status: Draft
Source: Migrated from spec/userstories.md (US-13)
User Scenarios & Testing
User Story 1 - Enforce Configured Event Cap on Creation (Priority: P1)
As a self-hoster, I want to configure a maximum number of simultaneously active events via a server environment variable, so that I can prevent storage exhaustion and limit potential abuse on my instance without modifying code.
Why this priority: The event cap is the primary deliverable of this story — without it, there is no feature. All other scenarios are edge cases of this core enforcement behavior.
Independent Test: Can be fully tested by configuring MAX_ACTIVE_EVENTS=1, creating one event, then attempting to create a second — the second creation should be rejected with a clear error.
Acceptance Scenarios:
- Given the server is configured with
MAX_ACTIVE_EVENTS=3and 3 non-expired events exist, When a user submits the event creation form, Then the server rejects the request with a clear error indicating the instance is at capacity, and the frontend surfaces this error on the creation form — not as a silent failure. - Given the server is configured with
MAX_ACTIVE_EVENTS=3and 2 non-expired events exist, When a user submits the event creation form, Then the request succeeds and the new event is created normally. - Given the server is configured with
MAX_ACTIVE_EVENTS=3and 3 non-expired events exist, but 1 is past its expiry date (awaiting cleanup), When a user submits the event creation form, Then the request succeeds — expired events do not count toward the limit.
User Story 2 - No Limit When Variable Is Unset (Priority: P2)
As a self-hoster running a personal or trusted-group instance, I want no event limit applied by default, so that I do not need to configure anything to run the app normally.
Why this priority: The default behavior (unlimited) must be safe and require no configuration. Self-hosters who do not need a cap should not have to think about this setting.
Independent Test: Can be fully tested by starting the server without MAX_ACTIVE_EVENTS set and verifying that multiple events can be created without rejection.
Acceptance Scenarios:
- Given the server has no
MAX_ACTIVE_EVENTSenvironment variable set, When any number of events are created, Then no capacity error is returned — event creation is unlimited. - Given the server has
MAX_ACTIVE_EVENTSset to an empty string, When events are created, Then no capacity error is returned — an empty value is treated the same as unset.
User Story 3 - Cap Is Enforced Server-Side Only (Priority: P2)
As a self-hoster, I want the event cap to be enforced exclusively on the server, so that it cannot be bypassed by a modified or malicious client.
Why this priority: Client-side enforcement alone would be trivially bypassable. The server is the authoritative enforcement point.
Independent Test: Can be fully tested by sending a direct HTTP POST to the event creation endpoint (bypassing the frontend entirely) when the cap is reached — the server must reject it.
Acceptance Scenarios:
- Given the configured cap is reached, When a direct HTTP POST is made to the event creation endpoint (bypassing the frontend), Then the server returns an error response indicating the instance is at capacity.
- Given the configured cap is reached, When no personal data is included in the rejection response or logs, Then the server returns only the rejection status — no PII is logged.
Edge Cases
- What happens when
MAX_ACTIVE_EVENTS=0? [NEEDS EXPANSION — treat as "no limit" or "reject all"? Clarify during implementation.] - What happens when
MAX_ACTIVE_EVENTSis set to a non-integer value? The server should fail fast at startup with a clear configuration error. - Race condition: two concurrent creation requests when the cap is at N-1. The server must handle this atomically — one request succeeds, the other is rejected.
- Expired events that have not yet been cleaned up must not count toward the limit. The check must query only non-expired events.
Requirements
Functional Requirements
- FR-001: The server MUST read a
MAX_ACTIVE_EVENTSenvironment variable at startup to determine the event creation cap. - FR-002: If
MAX_ACTIVE_EVENTSis set to a positive integer and the number of non-expired events equals or exceeds that value, the server MUST reject new event creation requests with a clear error response. - FR-003: The frontend MUST surface the capacity error on the event creation form — not as a silent failure or generic error.
- FR-004: If
MAX_ACTIVE_EVENTSis unset or empty, the server MUST apply no limit — event creation is unlimited. - FR-005: Only non-expired events MUST count toward the limit; expired events awaiting cleanup are excluded from the count.
- FR-006: The limit MUST be enforced server-side; client-side state or input cannot bypass it.
- FR-007: No personal data or PII MUST be logged when a creation request is rejected due to the cap.
- FR-008: The
MAX_ACTIVE_EVENTSenvironment variable MUST be documented in the README's self-hosting section (configuration table).
Key Entities
- Event (active count): The count of events whose
expiry_dateis in the future. This is the value checked againstMAX_ACTIVE_EVENTSat event creation time.
Success Criteria
Measurable Outcomes
- SC-001: When the cap is reached, a POST to the event creation endpoint returns an appropriate HTTP error status with a machine-readable error body.
- SC-002: The capacity error is displayed to the user on the creation form with a message that does not expose internal state or configuration values.
- SC-003: Creating events up to but not exceeding the cap succeeds without any change in behavior compared to uncapped instances.
- SC-004: The
MAX_ACTIVE_EVENTSvariable appears in the README configuration table with its type, default, and description documented. - SC-005: Expired events (past
expiry_date) are never counted toward the cap, verifiable by inspecting the query or checking behavior after expiry.