feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
@@ -19,6 +19,42 @@
|
||||
- `docs/modules/excititor/operations/chunk-api-user-guide.md`
|
||||
- `docs/modules/excititor/schemas/vex-chunk-api.yaml`
|
||||
- `docs/modules/evidence-locker/attestation-contract.md`
|
||||
- `docs/product-advisories/14-Dec-2025 - Smart-Diff Technical Reference.md` (for VEX emission contracts)
|
||||
|
||||
## VEX Emission Contracts (Sprint 3500)
|
||||
|
||||
The Excititor module handles VEX candidate emission for Smart-Diff:
|
||||
|
||||
### Namespace
|
||||
- `StellaOps.Excititor.VexEmission` - VEX candidate generation
|
||||
|
||||
### Key Types
|
||||
- `VexCandidateEmitter` - Generates VEX candidate statements
|
||||
- `VexCandidate` - A VEX statement candidate for review
|
||||
- `VexEmissionRule` - Rule matching for VEX emission
|
||||
- `IVexCandidateRepository` - Storage for VEX candidates
|
||||
|
||||
### VEX Emission Triggers
|
||||
| Trigger | Description | VEX Status |
|
||||
|---------|-------------|------------|
|
||||
| `sink_unreachable` | Vulnerability requires sink not present | `not_affected` candidate |
|
||||
| `entry_unreachable` | Vulnerable entry point unreachable | `not_affected` candidate |
|
||||
| `api_absent` | Vulnerable API not called | `not_affected` candidate |
|
||||
| `package_removed` | Vulnerable package removed | `fixed` candidate |
|
||||
| `version_upgraded` | Package upgraded past fix version | `fixed` candidate |
|
||||
| `patch_applied` | Security patch detected | `fixed` candidate |
|
||||
|
||||
### VEX Candidate Workflow
|
||||
1. Smart-Diff detects reachability flip or package change
|
||||
2. `VexCandidateEmitter` evaluates emission rules
|
||||
3. Matching rules generate `VexCandidate` with justification
|
||||
4. Candidates stored via `IVexCandidateRepository`
|
||||
5. Candidates surfaced in triage UI for review/approval
|
||||
|
||||
### Integration Points
|
||||
- Scanner SmartDiff triggers VEX emission on reachability changes
|
||||
- Candidates stored with `SmartDiffPredicate` reference for traceability
|
||||
- Approved candidates become formal VEX statements via Attestor
|
||||
|
||||
## Working Agreements
|
||||
- Determinism: canonical JSON ordering; stable pagination; UTC ISO-8601 timestamps; sort chunk edges deterministically.
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
-- Excititor Schema Migration 005: Partition timeline_events Table
|
||||
-- Sprint: SPRINT_3422_0001_0001 - Time-Based Partitioning
|
||||
-- Task: 4.1 - Create partitioned vex.timeline_events table
|
||||
-- Category: C (infrastructure change, requires maintenance window)
|
||||
--
|
||||
-- Purpose: Convert vex.timeline_events to a partitioned table for improved
|
||||
-- query performance on time-range queries and easier data lifecycle management.
|
||||
--
|
||||
-- Partition strategy: Monthly by occurred_at
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================================
|
||||
-- Step 1: Create partitioned timeline_events table
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vex.timeline_events_partitioned (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
project_id UUID,
|
||||
event_type TEXT NOT NULL,
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
actor TEXT,
|
||||
details JSONB DEFAULT '{}',
|
||||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (id, occurred_at)
|
||||
) PARTITION BY RANGE (occurred_at);
|
||||
|
||||
-- ============================================================================
|
||||
-- Step 2: Create initial partitions (past 6 months + 4 months ahead)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
v_start DATE;
|
||||
v_end DATE;
|
||||
v_partition_name TEXT;
|
||||
BEGIN
|
||||
-- Start from 6 months ago
|
||||
v_start := date_trunc('month', NOW() - INTERVAL '6 months')::DATE;
|
||||
|
||||
-- Create partitions until 4 months ahead
|
||||
WHILE v_start <= date_trunc('month', NOW() + INTERVAL '4 months')::DATE LOOP
|
||||
v_end := (v_start + INTERVAL '1 month')::DATE;
|
||||
v_partition_name := 'timeline_events_' || to_char(v_start, 'YYYY_MM');
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_class c
|
||||
JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE n.nspname = 'vex' AND c.relname = v_partition_name
|
||||
) THEN
|
||||
EXECUTE format(
|
||||
'CREATE TABLE vex.%I PARTITION OF vex.timeline_events_partitioned
|
||||
FOR VALUES FROM (%L) TO (%L)',
|
||||
v_partition_name, v_start, v_end
|
||||
);
|
||||
RAISE NOTICE 'Created partition vex.%', v_partition_name;
|
||||
END IF;
|
||||
|
||||
v_start := v_end;
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Create default partition for any data outside defined ranges
|
||||
CREATE TABLE IF NOT EXISTS vex.timeline_events_default
|
||||
PARTITION OF vex.timeline_events_partitioned DEFAULT;
|
||||
|
||||
-- ============================================================================
|
||||
-- Step 3: Create indexes on partitioned table
|
||||
-- ============================================================================
|
||||
|
||||
-- Composite index for tenant + time queries (most common access pattern)
|
||||
CREATE INDEX IF NOT EXISTS ix_timeline_part_tenant_time
|
||||
ON vex.timeline_events_partitioned (tenant_id, occurred_at DESC);
|
||||
|
||||
-- Entity lookup index
|
||||
CREATE INDEX IF NOT EXISTS ix_timeline_part_entity
|
||||
ON vex.timeline_events_partitioned (entity_type, entity_id);
|
||||
|
||||
-- Project-based queries
|
||||
CREATE INDEX IF NOT EXISTS ix_timeline_part_project
|
||||
ON vex.timeline_events_partitioned (project_id)
|
||||
WHERE project_id IS NOT NULL;
|
||||
|
||||
-- Event type filter
|
||||
CREATE INDEX IF NOT EXISTS ix_timeline_part_event_type
|
||||
ON vex.timeline_events_partitioned (event_type, occurred_at DESC);
|
||||
|
||||
-- BRIN index for efficient time-range scans (complements B-tree indexes)
|
||||
CREATE INDEX IF NOT EXISTS ix_timeline_part_occurred_at_brin
|
||||
ON vex.timeline_events_partitioned USING BRIN (occurred_at)
|
||||
WITH (pages_per_range = 32);
|
||||
|
||||
-- ============================================================================
|
||||
-- Step 4: Add partition to partition_mgmt tracking (if schema exists)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'partition_mgmt') THEN
|
||||
INSERT INTO partition_mgmt.managed_tables (
|
||||
schema_name,
|
||||
table_name,
|
||||
partition_key,
|
||||
partition_type,
|
||||
retention_months,
|
||||
months_ahead,
|
||||
created_at
|
||||
) VALUES (
|
||||
'vex',
|
||||
'timeline_events_partitioned',
|
||||
'occurred_at',
|
||||
'monthly',
|
||||
36, -- 3 year retention
|
||||
4, -- Create 4 months ahead
|
||||
NOW()
|
||||
) ON CONFLICT (schema_name, table_name) DO NOTHING;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- ============================================================================
|
||||
-- Migration Notes (for DBA to execute during maintenance window)
|
||||
-- ============================================================================
|
||||
-- After this migration, to complete the table swap:
|
||||
--
|
||||
-- 1. Stop writes to vex.timeline_events
|
||||
-- 2. Migrate existing data:
|
||||
-- INSERT INTO vex.timeline_events_partitioned
|
||||
-- SELECT * FROM vex.timeline_events;
|
||||
-- 3. Rename tables:
|
||||
-- ALTER TABLE vex.timeline_events RENAME TO timeline_events_old;
|
||||
-- ALTER TABLE vex.timeline_events_partitioned RENAME TO timeline_events;
|
||||
-- 4. Drop old table after verification:
|
||||
-- DROP TABLE vex.timeline_events_old;
|
||||
-- 5. Resume writes
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user