diff --git a/docs/contracts/architecture/VS-02_SLICE_SPEC.md b/docs/contracts/architecture/VS-02_SLICE_SPEC.md new file mode 100644 index 00000000..87f18129 --- /dev/null +++ b/docs/contracts/architecture/VS-02_SLICE_SPEC.md @@ -0,0 +1,155 @@ +# VS-02: Synchronize Security Master - Vertical Slice Specification + +**Slice ID:** VS-02 +**Batch:** 1 (depends on VS-00, which is complete) +**Status:** 📋 SPECIFICATION +**Created:** 2026-08-04 + +--- + +## Executive Summary + +Establish **Security Master** synchronization system that keeps role permissions and access control rules in sync across the platform. + +**User Goal:** Security team can push updated permission rules to all modules without manual intervention or service restart. + +**Non-Goal:** +- LDAP/Active Directory integration (Phase 3) +- Real-time webhook notifications (Phase 3) +- Audit trail of permission changes (separate feature) + +--- + +## Acceptance Criteria + +### 1. Security Master Data Model ✅ + +- [ ] **Roles:** Admin, Analyst, Trader, Viewer (from VS-01, immutable) +- [ ] **Permissions:** resource (domain), action (read/write/execute) +- [ ] **Role-Permission Mapping:** Many-to-many assignment +- [ ] **Access Control Rules:** Conditional rules (e.g., "Trader can execute only during market hours") +- [ ] **Temporal Validity:** effective_at, expires_at (time-based activation) + +### 2. Synchronization Mechanism ✅ + +- [ ] **Outbound:** Export permission rules to all modules +- [ ] **Inbound:** Poll for remote updates from security master +- [ ] **Conflict Resolution:** Last-write-wins OR centralized authority +- [ ] **Idempotency:** Multiple sync runs produce same result +- [ ] **Rollback:** Previous good state cached, can revert on error + +### 3. Data Integrity ✅ + +- [ ] **PIT Compliance:** published_at, revision tracking +- [ ] **Immutability:** Security rules never deleted, only versioned +- [ ] **Schema-Qualified:** All queries use security.rules, security.role_permissions +- [ ] **Transactional:** Batch updates atomic (all-or-nothing) + +### 4. API Contracts ✅ + +**Endpoint: POST /api/security/master/sync** +``` +Request: { fromVersion: int } +Response: 200 { version: int, rulesCount: int, syncedAt: timestamp } +Errors: 409 (version conflict), 503 (service unavailable) +Idempotency: Yes (version-based) +``` + +**Endpoint: GET /api/security/master/rules** +``` +Response: 200 { rules: [Rule], version: int, lastSyncAt: timestamp } +Errors: 401 (unauthorized), 503 (stale data >5min) +``` + +### 5. Event Publishing ✅ + +- [ ] **SecurityMasterSynced Event:** When sync completes +- [ ] **PermissionRuleUpdated Event:** Per-rule change notification +- [ ] **SyncError Event:** When sync fails +- [ ] **Correlation:** CorrelationId traces entire sync operation + +--- + +## Failure Modes & Recovery + +### Scenario 1: Network Timeout During Sync + +**Trigger:** Remote security master unreachable +**Expected:** Endpoint returns 503, keeps previous version +**Recovery:** Auto-retry every 30 seconds (exponential backoff) + +### Scenario 2: Conflict (Remote Version Ahead) + +**Trigger:** Local version 5, remote version 7 +**Expected:** 409 Conflict { requiredVersion: 7 } +**Recovery:** Application requests specific version 7 + +### Scenario 3: Partial Sync (Half Complete) + +**Trigger:** Database transaction fails mid-sync +**Expected:** Rollback all changes, version unchanged +**Recovery:** Next sync attempt starts fresh + +--- + +## Security Considerations + +- ✅ **Authentication:** Only authenticated services can call /sync +- ✅ **Authorization:** Only SecurityAdmin role can trigger sync +- ✅ **Audit:** Every sync logged with timestamp, version, rules changed +- ✅ **Encryption:** Rules transmitted over TLS, stored encrypted +- ✅ **Immutability:** Rules cannot be deleted (only versioned) + +--- + +## Performance SLAs + +| Metric | Target | +|--------|--------| +| Sync latency | <5 seconds | +| Rules query latency | <100ms (cached) | +| Rollback latency | <1 second | +| Max rules per sync | 10,000 | + +--- + +## Dependencies + +### Inbound (Blocked By) +- ✅ **VS-00:** Platform foundation (complete) +- ✅ **VS-01:** Role definitions (complete) + +### Outbound (Unblocks) +- 🔄 **VS-03:** Market data ingestion (uses VS-02's permission model) +- 🔄 **VS-04~08:** All downstream slices depend on consistent permissions + +--- + +## Component Breakdown (7 items) + +| Component | Status | +|-----------|--------| +| **GOV** | 📋 This spec | +| **DATA** | ⏳ Next: PIT-compliant schema | +| **DOMAIN** | ⏳ Next: Sync logic tests | +| **BE** | ⏳ REST endpoints | +| **ASYNC** | ⏳ Sync job + events | +| **FE** | ⏳ Rules dashboard | +| **TESTOPS** | ⏳ Integration tests | + +**Total Duration:** ~18-22 hours (wall-clock ~3 days) + +--- + +## Sign-Off + +| Role | Status | Date | +|------|--------|------| +| Architect | ✅ Draft | 2026-08-04 | +| Security | ⏳ Review | TBD | + +--- + +**Status:** 📋 **READY FOR DATA/DOMAIN/BE COMPONENTS** + +Next: VS-02_DATA_CONTRACT.md diff --git a/docs/contracts/data/VS-02_DATA_CONTRACT.md b/docs/contracts/data/VS-02_DATA_CONTRACT.md new file mode 100644 index 00000000..6eea81e3 --- /dev/null +++ b/docs/contracts/data/VS-02_DATA_CONTRACT.md @@ -0,0 +1,189 @@ +# VS-02: Security Master Data Contract + +**Slice:** VS-02 (SynchronizeSecurityMaster) +**Status:** 📋 SPECIFICATION +**Version:** 1.0 +**Created:** 2026-08-04 + +--- + +## Schema (3NF Write Model) + +### security.rules (Permission Rules) + +```sql +CREATE TABLE security.rules ( + id SERIAL PRIMARY KEY, + rule_name VARCHAR(100) NOT NULL UNIQUE, + resource VARCHAR(50) NOT NULL, -- 'users', 'portfolios', 'trades' + action VARCHAR(20) NOT NULL, -- 'read', 'write', 'execute' + description VARCHAR(255), + + -- Temporal & Versioning + version INT NOT NULL DEFAULT 1, + effective_at TIMESTAMP NOT NULL, + expires_at TIMESTAMP, + published_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- Audit + created_by_user_id UUID, + correlation_id VARCHAR(36), + + -- Constraints + CONSTRAINT valid_resource CHECK (resource IN ('users', 'portfolios', 'trades', 'models')), + CONSTRAINT valid_action CHECK (action IN ('read', 'write', 'execute', 'approve')), + CONSTRAINT temporal_order CHECK (effective_at <= published_at), + UNIQUE(rule_name, version) +); + +CREATE INDEX idx_rules_effective_published + ON security.rules(effective_at, published_at); +``` + +### security.role_permissions (Role-Permission Mapping) + +```sql +CREATE TABLE security.role_permissions ( + id BIGSERIAL PRIMARY KEY, + role_id INT NOT NULL REFERENCES identity.roles(id), + rule_id INT NOT NULL REFERENCES security.rules(id), + + -- Temporal + assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + published_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + removed_at TIMESTAMP, -- Soft delete + + -- Audit + correlation_id VARCHAR(36), + + -- Constraints + CONSTRAINT valid_removal CHECK (removed_at IS NULL OR removed_at >= assigned_at), + UNIQUE(role_id, rule_id) WHERE removed_at IS NULL +); + +CREATE INDEX idx_role_perms_active + ON security.role_permissions(role_id, removed_at); +``` + +### security.access_control_rules (Conditional Rules) + +```sql +CREATE TABLE security.access_control_rules ( + id BIGSERIAL PRIMARY KEY, + rule_id INT NOT NULL REFERENCES security.rules(id), + + -- Condition + condition_type VARCHAR(50) NOT NULL, -- 'time-based', 'location-based', 'mfa-required' + condition_value JSONB NOT NULL, -- {"startTime": "09:30", "endTime": "16:00"} + + -- Temporal + effective_at TIMESTAMP NOT NULL, + expires_at TIMESTAMP, + published_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT valid_condition_type CHECK (condition_type IN ('time-based', 'location-based', 'mfa-required')) +); +``` + +### security.sync_checkpoint (Sync History) + +```sql +CREATE TABLE security.sync_checkpoint ( + id BIGSERIAL PRIMARY KEY, + + -- Sync State + sync_version INT NOT NULL UNIQUE, -- Incremental version + total_rules INT NOT NULL, + synced_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- Idempotency + correlation_id VARCHAR(36) UNIQUE, + + -- Status + status VARCHAR(20) DEFAULT 'success' -- 'success', 'partial', 'failed' + CHECK (status IN ('success', 'partial', 'failed')), + + -- Rollback + previous_version INT REFERENCES security.sync_checkpoint(sync_version), + error_message VARCHAR(500) +); + +CREATE INDEX idx_sync_latest ON security.sync_checkpoint(synced_at DESC); +``` + +--- + +## PIT (Point-in-Time) Queries + +**Get current permissions for role:** +```sql +SELECT sr.rule_name, sr.resource, sr.action +FROM security.role_permissions rp +JOIN security.rules sr ON rp.rule_id = sr.id +WHERE rp.role_id = @roleId + AND rp.published_at <= @cutoff + AND rp.removed_at IS NULL + AND sr.effective_at <= @cutoff + AND (sr.expires_at IS NULL OR sr.expires_at > @cutoff); +``` + +**Get rules active at specific time:** +```sql +SELECT * FROM security.rules +WHERE published_at <= @cutoff + AND effective_at <= @cutoff + AND (expires_at IS NULL OR expires_at > @cutoff); +``` + +--- + +## CDC Events + +### SecurityMasterSynced + +```json +{ + "eventId": "UUID", + "eventType": "SecurityMasterSynced", + "syncVersion": 42, + "totalRules": 156, + "newRules": 3, + "modifiedRules": 5, + "syncedAt": "2026-08-04T12:00:00Z", + "correlationId": "sync-001" +} +``` + +### PermissionRuleUpdated + +```json +{ + "eventId": "UUID", + "eventType": "PermissionRuleUpdated", + "ruleId": 123, + "ruleName": "trader_execute_permission", + "action": "execute", + "version": 2, + "syncVersion": 42, + "correlationId": "sync-001" +} +``` + +--- + +## Acceptance Criteria Checklist + +- [ ] All tables created with 3NF normalization +- [ ] PIT queries tested (published_at, effective_at, expires_at) +- [ ] Append-only verified (no direct UPDATE on business keys) +- [ ] Soft-delete working (removed_at pattern) +- [ ] Sync checkpoint tracked (version-based idempotency) +- [ ] CDC events defined (SecurityMasterSynced, PermissionRuleUpdated) +- [ ] Conditional rules supported (time-based, location-based, MFA) +- [ ] Indexes created for performance + +--- + +**Status:** 📋 **READY FOR DOMAIN TESTS & BE IMPLEMENTATION** + +Next: VS-02 DOMAIN Tests (sync logic validation)