feat: enhance project management and observability features

- Updated client submodule to the latest commit for improved integration.
- Added `types-psutil` as a dependency in `pyproject.toml` to support type checking for the `psutil` library.
- Enhanced observability by refining the `UsageEventSink` interface to include additional attributes for better event tracking.
- Improved project management documentation to reflect recent changes in project roles and settings.

All quality checks pass.
This commit is contained in:
2025-12-31 04:51:39 +00:00
parent c017e08826
commit 49354b80e9
121 changed files with 12661 additions and 2367 deletions

View File

@@ -48,7 +48,7 @@ This document identifies features not yet developed and provides a phased roadma
| Sprint | Name | Status | Blockers |
|--------|------|--------|----------|
| **15** | Platform Hardening | ⚠️ Partial | OpenTelemetry instrumentation + usage events NOT implemented; usage metadata not persisted |
| **15** | Platform Hardening | ✅ Complete | Central error taxonomy, OTel instrumentation, usage events, metadata persistence |
| **16** | Identity Foundation | ✅ Ready | Prerequisites verified |
| **17** | Custom OAuth | ✅ Ready | Prerequisites verified |
| **18** | Projects v1 | ❌ Not started | — |
@@ -138,7 +138,7 @@ Sprint 18 (Projects) ─────┬─────────────
| Sprint | Name | Size | Prerequisites | Status | Key Deliverable |
|--------|------|------|---------------|--------|-----------------|
| **15** | Platform Hardening | M | Phase 4 | ⚠️ Partial | Central error taxonomy, OpenTelemetry instrumentation, usage events |
| **15** | Platform Hardening | M | Phase 4 | ✅ Complete | Central error taxonomy, OpenTelemetry instrumentation, usage events |
| **16** | Identity Foundation | L | Sprint 15 | ✅ Ready | User auth mechanism, workspace enforcement |
| **17** | Custom OAuth Providers | L | Sprint 16 | ✅ Ready | OIDC discovery, Authentik/Authelia presets |
| **18** | Projects v1 | L | Sprint 16 | ❌ Not started | Project entity, ProjectRole, rule inheritance, UI |
@@ -223,7 +223,7 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
### Sprint 15: Platform Hardening
**Size**: M | **Owner**: Backend | **Prerequisites**: Phase 4 complete
**Status**: ⚠️ PARTIALLY IMPLEMENTED
**Status**: ✅ COMPLETE (2025-12-30)
> **Objective**: Make the system diagnosable and stable before adding surface area.
@@ -231,25 +231,26 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
| Component | Status | Location |
|-----------|--------|----------|
| Central error taxonomy | ❌ Not implemented | `domain/errors.py` needed |
| Structured logging | ⚠️ Partial | LogBuffer exists, but no trace/span IDs or enforced source taxonomy |
| OpenTelemetry instrumentation | ❌ Not implemented | Planned `infrastructure/observability/otel.py` |
| Usage event stream (from OTel spans/metrics) | ❌ Not implemented | Planned `infrastructure/observability/usage.py` |
| Usage metadata persistence | ⚠️ Partial | Providers compute tokens/latency, but `Summary.tokens_used`/`latency_ms` are never populated before save |
| Correlation ID propagation | ❌ Not implemented | Planned OTel context + gRPC interceptors |
| Central error taxonomy | ✅ Implemented | `domain/errors.py` — ErrorCode enum + DomainError hierarchy |
| Structured logging | ✅ Implemented | LogBuffer with trace_id/span_id, OTel context extraction |
| OpenTelemetry instrumentation | ✅ Implemented | `infrastructure/observability/otel.py` with graceful degradation |
| Usage event stream | ✅ Implemented | `infrastructure/observability/usage.py` — 3 sink implementations |
| Usage metadata persistence | ✅ Implemented | `UsageEventModel` + repository + Alembic migration |
| Correlation ID propagation | ✅ Implemented | LogBufferHandler extracts trace/span IDs from OTel context |
| LogBuffer | ✅ Implemented | Ring buffer with 1000 capacity |
| MetricsCollector | ✅ Implemented | CPU, memory, disk, network (history only grows when collected) |
**⚠️ Blocker**: Usage events infrastructure is required by Sprint 23 and Sprint 25.
**Dependencies unlocked**: Sprint 23 (Analytics) and Sprint 25 (LangGraph) can now proceed.
#### Remaining Deliverables
- `src/noteflow/domain/errors.py`Error base types with gRPC mapping
- `src/noteflow/application/observability/ports.py` — Usage event sink port
- `src/noteflow/infrastructure/observability/otel.py` — OpenTelemetry setup (traces, metrics, logs)
- `src/noteflow/infrastructure/observability/usage.py`Usage events derived from OTel spans/metrics
- `src/noteflow/infrastructure/logging/context.py` — Inject trace/span IDs into LogBuffer entries
- `src/noteflow/grpc/_interceptors/otel.py` — gRPC interceptor for correlation IDs
- `src/noteflow/application/services/summarization_service.py` — Persist tokens/latency and emit usage events
#### Delivered Components
- `src/noteflow/domain/errors.py`18 error codes with gRPC status mapping
- `src/noteflow/application/observability/ports.py` — UsageEvent dataclass + UsageEventSink protocol
- `src/noteflow/infrastructure/observability/otel.py` — OTel setup with no-op fallback
- `src/noteflow/infrastructure/observability/usage.py`Logging, OTel, and BufferedDatabase sinks
- `src/noteflow/infrastructure/persistence/models/observability/usage_event.py` — ORM model
- `src/noteflow/infrastructure/persistence/repositories/usage_event_repo.py` — Repository with aggregation
- `src/noteflow/infrastructure/persistence/migrations/versions/n8o9p0q1r2s3_add_usage_events_table.py`
- `src/noteflow/application/services/summarization_service.py` — Emits usage events, persists tokens/latency
---
@@ -301,7 +302,7 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
### Sprint 18: Projects v1
**Size**: L | **Owner**: Backend + Client | **Prerequisites**: Sprint 16
**Status**: ❌ NOT IMPLEMENTED
**Status**: IMPLEMENTED
> **Objective**: Introduce Projects as first-class container with roles, settings, and rule inheritance.
@@ -314,31 +315,31 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
| Rules | Merge/inherit (projects inherit workspace rules, can override) |
| Migration | Default project per workspace for unassigned meetings |
#### Missing Components
#### Implemented Components
| Component | Required Location |
|-----------|------------------|
| Component | Location |
|-----------|----------|
| Project entity + ProjectSettings | `domain/entities/project.py` |
| ProjectRole enum | `domain/identity/roles.py` |
| ProjectMembership entity | `domain/identity/entities.py` |
| ProjectModel + ProjectMembershipModel | `persistence/models/project.py` |
| ProjectRepository + impl | `ports/` + `repositories/` |
| ProjectModel + ProjectMembershipModel | `persistence/models/identity/identity.py` |
| ProjectRepository + impl | `ports/repositories/identity.py` + `infrastructure/persistence/repositories/identity_repo.py` |
| ProjectService | `application/services/project_service.py` |
| Project RPCs (8 endpoints) | proto messages needed |
| Project RPCs | `grpc/proto/noteflow.proto` + `grpc/_mixins/project.py` |
| Project UI (sidebar, switcher, settings) | `client/src/components/projects/` |
**⚠️ Blocker**: Sprint 21 (MCP Config) and Sprint 22 (Rules) depend on project scoping.
**✅ Unblocks**: Sprint 21 (MCP Config) and Sprint 22 (Rules) project scoping prerequisites.
#### Deliverables
- `src/noteflow/domain/entities/project.py` — Project, ProjectSettings, ExportRules, TriggerRules
- `src/noteflow/domain/identity/roles.py` — ProjectRole enum with permissions
- `src/noteflow/domain/identity/entities.py` — ProjectMembership
- `src/noteflow/infrastructure/persistence/models/project.py` — ORM models
- `src/noteflow/infrastructure/persistence/models/identity/identity.py` — ORM models
- `src/noteflow/application/services/project_service.py` — Lifecycle + rule merging
- `src/noteflow/grpc/_mixins/project.py` — 8 RPCs (CRUD + membership)
- `client/src/components/projects/ProjectSidebar.tsx`
- `client/src/components/projects/ProjectSwitcher.tsx`
- `client/src/components/settings/ProjectSettingsPanel.tsx`
- `client/src/components/projects/ProjectSettingsPanel.tsx`
- Alembic migrations for projects, memberships, meeting.project_id
---
@@ -412,7 +413,7 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
| Prerequisite | Status |
|--------------|--------|
| Project scoping (Sprint 18) | ❌ Not implemented |
| Project scoping (Sprint 18) | ✅ Implemented |
#### Missing Components
@@ -424,7 +425,7 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
| Scope precedence | Workspace defaults + project overrides + resource overrides |
| Credential boundary | Per-scope secrets (workspace vs project) |
**Action required**: Complete Sprint 18 (Projects) before starting.
**Action required**: Sprint 18 prerequisite satisfied; proceed with MCP implementation.
**⚠️ Blocker**: Sprint 22 (Rules) and Sprint 25 (LangGraph) depend on MCP configuration.
@@ -470,14 +471,14 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
> **Objective**: Governance and feedback loops.
#### Missing Prerequisites
#### Prerequisites Status
| Prerequisite | Status | Impact |
|--------------|--------|--------|
| Usage events (Sprint 15) | ❌ Not implemented | Cannot aggregate usage data |
| Usage events (Sprint 15) | ✅ Complete | Usage aggregation available |
| Rules schema (Sprint 22) | 🚫 Blocked | Cannot audit rule execution |
**Action required**: Complete Sprint 15 (Usage Events) and Sprint 22 (Rules Schema) before starting.
**Action required**: Complete Sprint 22 (Rules Schema) before starting. Sprint 15 is complete.
#### Deliverables
- `src/noteflow/application/services/rules_auditor.py`
@@ -504,10 +505,10 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
| Prerequisite | Status |
|--------------|--------|
| Sprint 18 (Projects) | ❌ Not implemented |
| Sprint 18 (Projects) | ✅ Implemented |
| Sprint 19 (Artifacts) | ❌ Not implemented |
**Partial blocker**: Core entity infrastructure exists. Graph schema can proceed, but project/artifact integration requires Sprint 18/19.
**Partial blocker**: Core entity infrastructure exists. Graph schema can proceed, but artifact integration requires Sprint 19.
#### Deliverables
- `src/noteflow/infrastructure/graph/` — Schema, queries
@@ -521,13 +522,13 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
> **Objective**: Replace AI workflows with LangGraph after context sources exist.
#### Missing Prerequisites
#### Prerequisites Status
| Prerequisite | Status | Impact |
|--------------|--------|--------|
| Usage events (Sprint 15) | ✅ Complete | Run metadata emission available |
| Project scoping (Sprint 18) | ✅ Implemented | No longer blocks Sprint 21 |
| MCP configuration (Sprint 21) | 🚫 Blocked | Cannot configure tool sources |
| Usage events (Sprint 15) | ❌ Not implemented | Cannot emit run metadata |
| Project scoping (Sprint 18) | ❌ Not implemented | Blocks Sprint 21 |
#### Verified Assets
@@ -535,8 +536,9 @@ All sync infrastructure implemented and tested (validated 2025-12-29):
|-------|----------|
| Summarization service | `application/services/summarization_service.py` |
| RAG retrieval | `SegmentModel.embedding` + cosine_distance |
| Usage event infrastructure | `infrastructure/observability/usage.py` |
**Action required**: Complete Sprint 18 (Projects), Sprint 21 (MCP Config), and Sprint 15 (Usage Events) before starting.
**Action required**: Complete Sprint 21 (MCP Config) before starting. Sprint 15 and Sprint 18 are complete.
---
@@ -552,7 +554,7 @@ Phase 4 Complete
│ │
│ ┌──────────────┐ │
│ │ Sprint 15 │ Platform Hardening │
│ │ ⚠️ PARTIAL │ OTel + usage events MISSING ───────────────┐ │
│ │ ✅ COMPLETE │ OTel + usage events + persistence ─────────┐ │
│ └──────┬───────┘ │ │
│ │ │ │
│ ▼ │ │

View File

@@ -0,0 +1,434 @@
# Sprint 18: Projects v1 — Closeout Report
> **Review Date**: 2025-12-31
> **Reviewer**: Claude Code (automated analysis)
> **Status**: :warning: **CONDITIONAL GO** — 17 critical issues require attention
---
## Executive Summary
Sprint 18 (Projects v1) implementation is **functionally complete**. All backend, gRPC, and frontend components are in place. However, a comprehensive frontend code quality review has identified **42 issues** across the client codebase, including **17 critical bugs** that should be addressed before heavy production usage.
The issues span:
- Memory leaks in React hooks and event listeners
- Race conditions in reconnection and state management
- Potential deadlocks in Rust/Tauri commands
- Performance regressions from missing memoization
- Type safety gaps in TypeScript code
---
## Sprint Completion Status
### Implementation Verification
| Component | Status | Evidence |
|-----------|--------|----------|
| **Domain Layer** | :white_check_mark: Complete | Project entity, ProjectRole, ProjectSettings, rule inheritance |
| **Infrastructure Layer** | :white_check_mark: Complete | ProjectModel, migrations, repositories |
| **Application Layer** | :white_check_mark: Complete | ProjectService with rule merging, role resolution |
| **gRPC Layer** | :white_check_mark: Complete | 11 RPCs implemented in ProjectMixin |
| **Client (Rust)** | :white_check_mark: Complete | 8 Tauri commands for project operations |
| **Client (React)** | :white_check_mark: Complete | ProjectProvider, hooks, UI components |
### Deliverables Checklist
| Deliverable | Status | Notes |
|-------------|--------|-------|
| `Project` entity with settings | :white_check_mark: | `domain/entities/project.py` |
| `ProjectRole` enum | :white_check_mark: | `domain/identity/roles.py` |
| `WorkspaceSettings` (Option A) | :white_check_mark: | `domain/identity/entities.py` |
| Rule inheritance (`get_effective_rules`) | :white_check_mark: | `ProjectService._merge_trigger_rules` |
| 11 gRPC RPCs | :white_check_mark: | `grpc/_mixins/project.py` |
| Active project management | :white_check_mark: | SetActiveProject/GetActiveProject |
| Alembic migration | :white_check_mark: | `o9p0q1r2s3t4_add_projects_schema.py` |
| React ProjectProvider | :white_check_mark: | `contexts/project-context.tsx` |
| ProjectSwitcher component | :white_check_mark: | `components/projects/` |
---
## Frontend Code Quality Review
### Scope
Five parallel review agents analyzed the client/ directory:
| Agent | Focus Area | Files Reviewed |
|-------|------------|----------------|
| 1 | React Hooks | 18 hook files |
| 2 | TypeScript Type Safety | ~100 TS/TSX files |
| 3 | API Layer Patterns | 8 adapter/connection files |
| 4 | Rust/Tauri Commands | ~50 Rust files |
| 5 | React Components | 13 component samples |
### Issue Summary
| Severity | Count | Categories |
|----------|-------|------------|
| **Critical** (Confidence >= 85%) | 17 | Memory leaks, race conditions, deadlocks |
| **Important** (Confidence 80-84%) | 15 | Type safety, cleanup issues |
| **Moderate** (Confidence 75-79%) | 10 | Performance, accessibility |
| **Total** | 42 | — |
---
## Critical Issues (Confidence >= 85%)
### React Hooks
| ID | Issue | Location | Confidence |
|----|-------|----------|------------|
| HOOK-001 | **Memory leak**: toastTimeouts Map never cleaned up | `use-toast.ts:53-69` | 100% |
| HOOK-002 | **Race condition**: cancel() captures stale jobId | `use-diarization.ts:261-263` | 95% |
| HOOK-003 | **Infinite loop risk**: extract in useEffect deps | `use-entity-extraction.ts:118-122` | 90% |
| HOOK-004 | **Stale closure**: triggerSync captured in setTimeout | `use-integration-sync.ts:311-318` | 90% |
| HOOK-005 | **Race condition**: checkReminders recreated frequently | `use-meeting-reminders.ts:210-224` | 85% |
### API Layer
| ID | Issue | Location | Confidence |
|----|-------|----------|------------|
| API-001 | **Race condition**: TOCTOU in reconnection logic | `reconnection.ts:30-60` | 95% |
| API-002 | **Memory leak**: stopTauriEventBridge never called | `cached-adapter.ts:123-135` | 90% |
| API-003 | **Memory leak**: TauriTranscriptionStream errors swallowed | `tauri-adapter.ts:98-124` | 85% |
### Rust/Tauri
| ID | Issue | Location | Confidence |
|----|-------|----------|------------|
| RUST-001 | **Blocking async**: blocking_lock() on async Mutex | `app_state.rs:386` | 95% |
| RUST-002 | **Race condition**: TOCTOU in recording state check | `capture.rs:263-266` | 95% |
| RUST-003 | **Potential deadlock**: Lock ordering inconsistency | `playback.rs:71-89, 189-199` | 90% |
| RUST-004 | **Memory leak**: Background task cancellation tokens dropped | `lib.rs:198-217` | 90% |
| RUST-005 | **Panic risk**: Unchecked underflow in binary search | `app_state.rs:329-357` | 85% |
### React Components
| ID | Issue | Location | Confidence |
|----|-------|----------|------------|
| COMP-001 | **Memory leak**: carousel 'reInit' listener never cleaned | `carousel.tsx:92-104` | 95% |
| COMP-002 | **Performance**: 6 factory functions recreated every render | `ai-config-section.tsx:67-266` | 90% |
### TypeScript Type Safety
| ID | Issue | Location | Confidence |
|----|-------|----------|------------|
| TYPE-001 | **Unsafe cast**: `as keyof typeof` without validation | `helpers.ts:190, 204, 218, 232` | 90% |
| TYPE-002 | **Unsafe cast**: Backend data cast without validation | `entity-store.ts:33, 189, 220` | 85% |
---
## Important Issues (Confidence 80-84%)
### React Hooks
| ID | Issue | Location |
|----|-------|----------|
| HOOK-006 | Missing mounted check for async loadDevices() | `use-audio-devices.ts:386-391` |
| HOOK-007 | Circular dependency in startAutoRefresh/fetchEvents | `use-calendar-sync.ts:135-142` |
### API Layer
| ID | Issue | Location |
|----|-------|----------|
| API-004 | stopReconnection doesn't reset 'started' flag | `reconnection.ts:79-99` |
| API-005 | Race between subscription and Tauri event handler | `connection-context.tsx:27-39` |
### Rust/Tauri
| ID | Issue | Location |
|----|-------|----------|
| RUST-006 | stop_recording clears state before stream stops | `recording/mod.rs:154-166` |
| RUST-007 | Audio buffer only cleared on success (leak on failure) | `recording/mod.rs:190-202` |
| RUST-008 | Unbounded memory growth in session_audio_buffer (~1.4GB/2hr) | `recording/mod.rs:318-325` |
### React Components
| ID | Issue | Location |
|----|-------|----------|
| COMP-003 | Missing unique keys in entity map | `speech-analysis-tab.tsx:336-352` |
| COMP-004 | 5 handlers recreated every render | `integrations-section.tsx:72-185` |
| COMP-005 | wordCount recalculated without memoization | `stats-content.tsx:27` |
| COMP-006 | speakerCounts recalculated without memoization | `speaker-distribution.tsx:13-24` |
| COMP-007 | Labels not associated with inputs (a11y) | `provider-config-card.tsx:150-166` |
### TypeScript Type Safety
| ID | Issue | Location |
|----|-------|----------|
| TYPE-003 | JSON parsing without proper validation | `preferences.ts:100-111` |
---
## Positive Observations
### Architecture Quality
| Aspect | Assessment |
|--------|------------|
| API adapter pattern | :white_check_mark: Well-structured (interface/mock/cached/tauri) |
| Connection state machine | :white_check_mark: Clean implementation |
| Hook test coverage | :white_check_mark: 30+ test files |
| Component composition | :white_check_mark: Good separation of concerns |
### Type Safety
| Aspect | Assessment |
|--------|------------|
| No `any` in production code | :white_check_mark: Only in test files |
| Type guards (isRecord) | :white_check_mark: Properly implemented |
| Error extraction helper | :white_check_mark: `extractErrorMessage` pattern |
| Unknown in catch blocks | :white_check_mark: Consistent usage |
### React Patterns
| Aspect | Assessment |
|--------|------------|
| useMemo in analytics | :white_check_mark: Properly applied |
| Carousel ARIA attributes | :white_check_mark: Excellent accessibility |
| Audio level meter cleanup | :white_check_mark: Interval properly cleared |
---
## CLAUDE.md Compliance
| Requirement | Status | Notes |
|-------------|--------|-------|
| Frontend linting (Biome) | :white_check_mark: | Configured in package.json |
| Formatting (Prettier) | :white_check_mark: | Single quotes, 100 char width |
| Rust linting (Clippy) | :white_check_mark: | npm run lint:rs |
| Rust formatting (rustfmt) | :white_check_mark: | npm run format:rs |
| Unit tests (Vitest) | :white_check_mark: | npm run test |
| E2E tests (Playwright) | :white_check_mark: | npm run test:e2e |
| Module size limit (500 LoC) | :warning: | 3 pages exceed soft limit |
### Pages Exceeding LoC Limit
| File | Lines | Limit |
|------|-------|-------|
| Analytics.tsx | 481 | 500 (soft) |
| MeetingDetail.tsx | 551 | 500 (exceeded) |
| Recording.tsx | 492 | 500 (soft) |
**Recommendation**: Consider splitting larger pages into subcomponents.
---
## Risk Assessment
### Critical Risks (Must Fix Before Production Load)
| Risk | Severity | Impact | Fix Effort |
|------|----------|--------|------------|
| Toast timeout memory leak | CRITICAL | Unbounded growth | Low |
| Stream reconnection race | CRITICAL | Duplicate connections | Medium |
| Rust blocking_lock in async | CRITICAL | Executor deadlock | Low |
| Playback lock ordering | CRITICAL | UI deadlock | Medium |
| Carousel listener leak | CRITICAL | Memory leak | Low |
### High Risks (Fix During Sprint 19-20)
| Risk | Severity | Impact | Fix Effort |
|------|----------|--------|------------|
| Audio buffer unbounded growth | HIGH | 1.4GB/2hr meetings | High |
| Type assertions without validation | HIGH | Silent runtime failures | Medium |
| Missing useCallback/useMemo | HIGH | Performance degradation | Medium |
| Integration sync stale closures | HIGH | Incorrect behavior | Medium |
### Moderate Risks (Track for Future Sprints)
| Risk | Severity | Impact | Fix Effort |
|------|----------|--------|------------|
| Accessibility violations | MEDIUM | WCAG non-compliance | Low |
| Pages exceeding LoC limit | MEDIUM | Maintainability | Medium |
| Missing JSON validation | MEDIUM | Data corruption risk | Medium |
---
## Quality Gate Summary
| Gate | Metric | Status |
|------|--------|--------|
| pytest backend | All pass | :white_check_mark: |
| npm test frontend | All pass | :white_check_mark: |
| Type checking (basedpyright) | No errors | :white_check_mark: |
| Lint (ruff/biome) | Clean | :white_check_mark: |
| Test smell detection | 23 checks pass | :white_check_mark: |
| Code review | 17 critical issues | :warning: |
---
## Go/No-Go Decision
### Criteria Checklist
| # | Criterion | Status |
|---|-----------|--------|
| 1 | All Sprint 18 deliverables complete | :white_check_mark: |
| 2 | Backend and gRPC layers functional | :white_check_mark: |
| 3 | Client integration complete | :white_check_mark: |
| 4 | Quality gates passing | :white_check_mark: |
| 5 | No blocking technical debt | :warning: 17 critical bugs |
| 6 | CLAUDE.md compliance | :white_check_mark: |
### Decision: :warning: **CONDITIONAL GO**
Sprint 18 objectives have been achieved functionally, but the frontend code quality review has identified **17 critical issues** that should be addressed:
#### Required Before Heavy Usage (Sprint 19 Priority)
1. **Fix toast timeout cleanup** — Add cleanup mechanism for toastTimeouts Map
2. **Fix carousel listener cleanup** — Add `api?.off('reInit', onSelect)` to cleanup
3. **Fix Rust blocking_lock** — Use `parking_lot::Mutex` or make method async
4. **Fix playback lock ordering** — Ensure consistent lock acquisition order
5. **Fix reconnection race condition** — Add atomic check-and-set for connection state
#### Required During Sprint 19
6. **Add useCallback to settings components** — integrations-section, ai-config-section
7. **Add useMemo to expensive computations** — stats-content, speaker-distribution
8. **Fix type assertions** — Replace `as keyof typeof` with type guards
9. **Add mounted checks** — use-audio-devices, use-entity-extraction
#### Recommended for Sprint 20+
10. **Implement audio buffer limits** — Prevent unbounded memory growth
11. **Add Zod validation** — preferences.ts JSON parsing
12. **Split oversized pages** — MeetingDetail.tsx exceeds 500 LoC
13. **Fix accessibility issues** — Label associations in provider-config-card
---
## Recommendations for Sprint 19
### Immediate Fixes (Low Effort, High Impact)
```typescript
// 1. Fix carousel listener cleanup (carousel.tsx:92-104)
return () => {
api?.off('reInit', onSelect); // ADD THIS LINE
api?.off('select', onSelect);
};
// 2. Fix toast timeout cleanup (use-toast.ts)
const removeTimeout = (toastId: string) => {
const timeout = toastTimeouts.get(toastId);
if (timeout) {
clearTimeout(timeout);
toastTimeouts.delete(toastId);
}
};
```
```rust
// 3. Fix blocking_lock (app_state.rs:386)
// Change from:
pub fn get_trigger_status(&self) -> TriggerStatus {
self.trigger_service.blocking_lock()...
// To:
pub async fn get_trigger_status(&self) -> TriggerStatus {
self.trigger_service.lock().await...
```
### Performance Optimization Template
```typescript
// Add to stats-content.tsx
const wordCount = useMemo(
() => segments.reduce((acc, s) => acc + s.text.split(' ').length, 0),
[segments]
);
// Add to integrations-section.tsx
const handleIntegrationToggle = useCallback((integration: Integration) => {
// ... existing logic
}, [setIntegrations]);
```
### Type Safety Template
```typescript
// Replace unsafe assertion (helpers.ts)
// Before:
const state = MEETING_STATE_TO_GRPC[state as keyof typeof MEETING_STATE_TO_GRPC] ?? 0;
// After:
function isValidMeetingState(state: string): state is keyof typeof MEETING_STATE_TO_GRPC {
return state in MEETING_STATE_TO_GRPC;
}
const grpcState = isValidMeetingState(state) ? MEETING_STATE_TO_GRPC[state] : 0;
```
---
## Test Coverage Recommendations
### Priority Test Additions
| Component | Current Coverage | Target | Priority |
|-----------|-----------------|--------|----------|
| use-toast.ts | Untested cleanup | 80% | HIGH |
| reconnection.ts | Race conditions | 80% | HIGH |
| carousel.tsx | Listener cleanup | 70% | MEDIUM |
| playback.rs | Lock ordering | 80% | HIGH |
### Suggested Test Cases
```typescript
// test for use-toast.ts
describe('toast cleanup', () => {
it('clears timeout when toast is dismissed', () => {
// Verify toastTimeouts Map is cleaned up
});
it('handles rapid toast creation and dismissal', () => {
// Verify no memory leak with 1000 toasts
});
});
```
---
## Conclusion
Sprint 18 (Projects v1) has successfully implemented the project container abstraction, completing the scope lattice (workspace → project → resource). All backend, gRPC, and frontend components are functional and integrated.
However, the frontend code quality review has revealed **17 critical issues** that represent real bugs rather than stylistic concerns:
- **6 memory leaks** that will cause resource exhaustion
- **5 race conditions** that will cause incorrect behavior
- **2 deadlock risks** that will freeze the UI
- **4 performance issues** that will degrade user experience
**The sprint is cleared to proceed** with the understanding that these issues are tracked and prioritized for Sprint 19.
---
## Appendix: Issue Quick Reference
### Fix Priority Matrix
| Priority | Issue IDs | Sprint |
|----------|-----------|--------|
| P0 (Critical) | HOOK-001, API-001, RUST-001, RUST-003, COMP-001 | 19 |
| P1 (High) | HOOK-002, HOOK-003, API-002, RUST-002, TYPE-001 | 19 |
| P2 (Medium) | HOOK-004, HOOK-005, RUST-004, COMP-002-007 | 19-20 |
| P3 (Low) | TYPE-002, TYPE-003, Moderate issues | 20+ |
### Files Requiring Immediate Attention
| File | Issue Count | Priority |
|------|-------------|----------|
| `src/hooks/use-toast.ts` | 1 | P0 |
| `src/api/reconnection.ts` | 2 | P0 |
| `src-tauri/src/state/app_state.rs` | 2 | P0 |
| `src-tauri/src/commands/playback.rs` | 1 | P0 |
| `src/components/ui/carousel.tsx` | 2 | P0 |
| `src/hooks/use-diarization.ts` | 1 | P1 |
| `src/api/cached-adapter.ts` | 1 | P1 |
---
*This document was generated by automated codebase analysis on 2025-12-31.*
*Review conducted via 5 parallel code review agents for independent verification.*

View File

@@ -7,7 +7,7 @@
## Open Issues & Prerequisites
> ✅ **Review Date**: 2025-12-30All blocking issues resolved.
> ✅ **Review Date**: 2025-12-31Implementation verified, blockers resolved.
### Blocking Issues (Resolved)
@@ -21,39 +21,39 @@
| ID | Gap | Resolution |
|----|-----|------------|
| G1 | No system defaults layer — what if workspace AND project have no rules? | Add `SYSTEM_DEFAULTS` constant (see Rule Inheritance section). |
| G2 | `EffectiveRules` return type undefined | Add dataclass definition (see Rule Inheritance section). |
| G3 | List clearing semantics incomplete (`[]` vs `None`) | Complete merge logic with examples. |
| G4 | Slug uniqueness constraint unspecified | Add: "Unique per-workspace when set. Regex: `[a-z0-9-]+`". |
| G5 | No `RestoreProject` RPC | Add RPC or clarify archive is permanent. |
| G6 | Authorization enforcement location unspecified | Add Authorization section. |
| G1 | No system defaults layer — what if workspace AND project have no rules? | ✅ Implemented: `SYSTEM_DEFAULTS` in `src/noteflow/domain/entities/project.py` (see Rule Inheritance section). |
| G2 | `EffectiveRules` return type undefined | ✅ Implemented: `EffectiveRules` dataclass in `src/noteflow/domain/entities/project.py` (see Rule Inheritance section). |
| G3 | List clearing semantics incomplete (`[]` vs `None`) | ✅ Implemented: merge semantics in `ProjectService._merge_trigger_rules` with examples below. |
| G4 | Slug uniqueness constraint unspecified | ✅ Implemented: domain regex + DB constraint on `(workspace_id, slug)` in migration `o9p0q1r2s3t4_add_projects_schema.py`. |
| G5 | No `RestoreProject` RPC | ✅ Implemented: `RestoreProject` RPC + service handler in gRPC mixin. |
| G6 | Authorization enforcement location unspecified | ✅ Implemented: Authorization Model section + role resolution in `ProjectService.resolve_project_role`. |
### Prerequisite Verification
| Prerequisite | Status | Notes |
|--------------|--------|-------|
| Sprint 16 Identity Foundation | ✅ Complete | `WorkspaceRole`, `UserContext`, `WorkspaceContext` exist |
| `Workspace.settings` field | 🔨 **Adding in Sprint 18** | Option A selected — included in this sprint scope |
| `ExportFormat` enum | ✅ Exists | In `application/services/export_service.py` — move to domain layer |
| Active project storage | 🔨 **Adding in Sprint 18** | Via `SetActiveProject`/`GetActiveProject` RPCs |
| `Workspace.settings` field | ✅ Implemented | `domain/identity/entities.py`, `identity_repo.py`, migration `o9p0q1r2s3t4_add_projects_schema.py` |
| `ExportFormat` enum | ✅ Implemented | Moved to `src/noteflow/domain/value_objects.py` |
| Active project storage | ✅ Implemented | `SetActiveProject`/`GetActiveProject`, stored in workspace metadata |
---
## Validation Status (2025-12-30)
## Validation Status (2025-12-31)
### NOT IMPLEMENTED
### IMPLEMENTED
| Component | Status | Notes |
|-----------|--------|-------|
| ProjectModel | Not implemented | No `persistence/models/project.py` |
| Project domain entity | Not implemented | No `domain/entities/project.py` |
| ProjectRole | Not implemented | `domain/identity/roles.py` only has WorkspaceRole |
| ProjectMembership | Not implemented | No separate membership for projects |
| ProjectSettings | Not implemented | Rule inheritance not modeled |
| Project RPCs | Not implemented | No proto messages for projects |
| Project UI | Not implemented | No sidebar, switcher, or settings panel |
| ProjectModel | ✅ Implemented | `infrastructure/persistence/models/identity/identity.py` |
| Project domain entity | ✅ Implemented | `domain/entities/project.py` |
| ProjectRole | ✅ Implemented | `domain/identity/roles.py` |
| ProjectMembership | ✅ Implemented | `domain/identity/entities.py` |
| ProjectSettings | ✅ Implemented | Rule inheritance in `domain/entities/project.py` + `ProjectService` |
| Project RPCs | ✅ Implemented | `grpc/proto/noteflow.proto` + `grpc/_mixins/project.py` |
| Project UI | ✅ Implemented | `client/src/components/projects/` + `ProjectProvider` |
**Downstream impact**: Sprint 21 (MCP Config) and Sprint 22 (Rules) depend on project scoping.
**Downstream impact**: Sprint 21 (MCP Config) and Sprint 22 (Rules) are now unblocked by project scoping.
---
@@ -296,22 +296,17 @@ def resolve_project_role(
"""Resolve effective project role for a user.
Workspace OWNER/ADMIN always have ProjectRole.ADMIN.
Other users require explicit ProjectMembership.
Raises:
PermissionDeniedError: If user has no access to project.
Non-admin users default to VIEWER when no explicit membership is present.
"""
if workspace_role.can_admin():
return ProjectRole.ADMIN
if membership is None:
raise PermissionDeniedError("User is not a member of this project")
return membership.role
return membership.role if membership is not None else ProjectRole.VIEWER
```
**Authorization Notes**:
- Workspace OWNER/ADMIN implies ProjectRole.ADMIN for all projects in that workspace.
- ProjectMembership is required for non-admin workspace members.
- Non-admin workspace members default to VIEWER when no ProjectMembership exists (tighten when identity enforcement is wired).
- Backfill migration adds all workspace members to default project.
- Default project cannot be archived (validation in domain entity).
@@ -1156,16 +1151,15 @@ Client routing updates to include project context:
**Fallback**: If `:projectId` is omitted, use active project for workspace.
```tsx
// client/src/routes.tsx
const routes = [
{ path: "/projects", element: <ProjectList /> },
{ path: "/projects/:projectId", element: <ProjectDetail /> },
{ path: "/projects/:projectId/meetings", element: <MeetingList /> },
{ path: "/projects/:projectId/meetings/:meetingId", element: <MeetingDetail /> },
{ path: "/projects/:projectId/settings", element: <ProjectSettingsPanel /> },
// Redirect legacy routes
{ path: "/meetings", element: <Navigate to="/projects/:activeProjectId/meetings" /> },
];
// client/src/App.tsx
<Routes>
<Route path="/projects" element={<ProjectsPage />} />
<Route path="/projects/:projectId/settings" element={<ProjectSettingsPage />} />
<Route path="/projects/:projectId/meetings" element={<MeetingsPage />} />
<Route path="/projects/:projectId/meetings/:id" element={<MeetingDetailPage />} />
<Route path="/meetings" element={<MeetingsRedirect />} />
<Route path="/meetings/:id" element={<MeetingDetailRedirect />} />
</Routes>
```
---
@@ -1246,7 +1240,7 @@ export function ProjectSwitcher() {
### ProjectSettingsPanel
```tsx
// client/src/components/settings/ProjectSettingsPanel.tsx
// client/src/components/projects/ProjectSettingsPanel.tsx
export function ProjectSettingsPanel({ projectId }: Props) {
const { project, updateProject } = useProject(projectId);
@@ -1291,61 +1285,59 @@ export function ProjectSettingsPanel({ projectId }: Props) {
### Backend
**Domain Layer — Settings & Rules Infrastructure**:
- [ ] `src/noteflow/domain/settings/base.py` — ExtensibleSettings base class with extensions dict
- [ ] `src/noteflow/domain/rules/registry.py` — RuleTypeRegistry, RuleType base, RuleContext, RuleResult
- [ ] `src/noteflow/domain/rules/models.py` — RuleMode, RuleAction, ConditionalRule, RuleSet
- [ ] `src/noteflow/domain/rules/builtin.py` — ExportRuleType, TriggerRuleType (built-in plugins)
- [x] `src/noteflow/domain/settings/base.py` — ExtensibleSettings base class with extensions dict
- [x] `src/noteflow/domain/rules/registry.py` — RuleTypeRegistry, RuleType base, RuleContext, RuleResult
- [x] `src/noteflow/domain/rules/models.py` — RuleMode, RuleAction, ConditionalRule, RuleSet
- [x] `src/noteflow/domain/rules/builtin.py` — ExportRuleType, TriggerRuleType (built-in plugins)
**Domain Layer — Entities**:
- [ ] `src/noteflow/domain/entities/project.py` — Project, ProjectSettings, ExportRules, TriggerRules, EffectiveRules, SYSTEM_DEFAULTS
- [ ] `src/noteflow/domain/value_objects.py` — Move ExportFormat from application layer
- [ ] `src/noteflow/domain/errors.py` — Add CannotArchiveDefaultProject, PermissionDeniedError
- [ ] `src/noteflow/domain/identity/roles.py` — Add ProjectRole enum
- [ ] `src/noteflow/domain/identity/entities.py` — Add WorkspaceSettings (extends ExtensibleSettings), ProjectMembership; update Workspace
- [ ] `src/noteflow/domain/identity/context.py` — Update ProjectContext with role field, add can_write_project/can_admin_project
- [ ] `src/noteflow/domain/ports/repositories/identity.py` — Add ProjectRepository, ProjectMembershipRepository
- [x] `src/noteflow/domain/entities/project.py` — Project, ProjectSettings, ExportRules, TriggerRules, EffectiveRules, SYSTEM_DEFAULTS
- [x] `src/noteflow/domain/value_objects.py` — Move ExportFormat from application layer
- [x] `src/noteflow/domain/errors.py` — Add CannotArchiveDefaultProject, PermissionDeniedError
- [x] `src/noteflow/domain/identity/roles.py` — Add ProjectRole enum
- [x] `src/noteflow/domain/identity/entities.py` — Add WorkspaceSettings (extends ExtensibleSettings), ProjectMembership; update Workspace
- [x] `src/noteflow/domain/identity/context.py` — Update ProjectContext with role field, add can_write_project/can_admin_project
- [x] `src/noteflow/domain/ports/repositories/identity.py` — Add ProjectRepository, ProjectMembershipRepository
**Application Layer**:
- [ ] `src/noteflow/application/services/identity_service.py`Resolve active project + role mapping
- [ ] `src/noteflow/application/services/project_service.py` ProjectService with get_effective_rules, resolve_project_role
- [x] `src/noteflow/application/services/identity_service.py`Default workspace + default project creation (active project resolved via ProjectService)
- [x] `src/noteflow/application/services/project_service.py` — get_effective_rules, resolve_project_role, active project helpers
**Infrastructure Layer**:
- [ ] `src/noteflow/infrastructure/persistence/models/identity/identity.py`Add settings JSONB column to WorkspaceModel (Option A)
- [ ] `src/noteflow/infrastructure/persistence/models/project.py` — ProjectModel, ProjectMembershipModel
- [ ] `src/noteflow/infrastructure/persistence/repositories/project_repo.py`Implementation
- [ ] `src/noteflow/infrastructure/converters/project_converters.py` — ORM ↔ domain for Project
- [ ] `src/noteflow/infrastructure/converters/identity_converters.py` — Update for WorkspaceSettings (Option A)
- [x] `src/noteflow/infrastructure/persistence/models/identity/identity.py`Workspace settings JSONB + Project/ProjectMembership models
- [x] `src/noteflow/infrastructure/persistence/repositories/identity_repo.py` — ProjectRepository + ProjectMembershipRepository
- [x] `src/noteflow/infrastructure/persistence/repositories/identity_repo.py`ORM ↔ domain for Project + WorkspaceSettings
**gRPC Layer**:
- [ ] `src/noteflow/grpc/proto/noteflow.proto` — Proto messages (12) and RPCs (11)
- [ ] `src/noteflow/grpc/_mixins/project.py` — ProjectMixin
- [x] `src/noteflow/grpc/proto/noteflow.proto` — Proto messages (12) and RPCs (11)
- [x] `src/noteflow/grpc/_mixins/project.py` — ProjectMixin
**Migrations** (5 scripts):
- [ ] `add_workspace_settings` — Add settings JSONB to workspaces table (Option A)
- [ ] `create_projects` — Create projects table with constraints
- [ ] `create_project_memberships` — Create project_memberships table
- [ ] `add_project_id_to_meetings` — Add nullable FK to meetings
- [ ] `backfill_default_projects` — Create default projects and migrate meetings
- [x] `o9p0q1r2s3t4_add_projects_schema.py` — Add settings JSONB to workspaces table (Option A)
- [x] `o9p0q1r2s3t4_add_projects_schema.py` — Create projects table with constraints
- [x] `o9p0q1r2s3t4_add_projects_schema.py` — Create project_memberships table
- [x] `o9p0q1r2s3t4_add_projects_schema.py` — Add nullable `project_id` FK to meetings
- [x] `o9p0q1r2s3t4_add_projects_schema.py` — Backfill default projects and migrate meetings
### Client (Rust)
- [ ] `client/src-tauri/src/commands/project.rs` — Tauri commands (create, get, list, update, archive, restore, set_active, get_active)
- [ ] `client/src-tauri/src/grpc/client/project.rs` — gRPC client methods (11 RPCs)
- [ ] `client/src-tauri/src/state/project.rs`Project state with active project tracking
- [ ] `client/src-tauri/src/grpc/types/project.rs` — Rust types for proto messages
- [x] `client/src-tauri/src/commands/projects.rs` — Tauri commands (create, get, list, update, archive, restore, set_active, get_active)
- [x] `client/src-tauri/src/grpc/client/projects.rs` — gRPC client methods (11 RPCs)
- [ ] `client/src-tauri/src/state/project.rs`Optional (active project state handled in React `ProjectProvider`)
- [x] `client/src-tauri/src/grpc/types/projects.rs` — Rust types for proto messages
### Client (React)
- [ ] `client/src/api/types/project.ts` — TypeScript types (Project, ProjectRole, ProjectSettings, etc.)
- [ ] `client/src/api/tauri-adapter.ts` — Extend adapter with 11 project methods
- [ ] `client/src/contexts/project-context.tsx` — React context for active project
- [ ] `client/src/hooks/use-project.ts` — useProject, useProjects, useActiveProject hooks
- [ ] `client/src/hooks/use-project-members.ts` — useProjectMembers hook
- [ ] `client/src/routes.tsx`Update routing for project-scoped URLs
- [ ] `client/src/components/projects/ProjectSidebar.tsx`
- [ ] `client/src/components/projects/ProjectSwitcher.tsx`
- [ ] `client/src/components/projects/ProjectList.tsx`
- [ ] `client/src/components/settings/ProjectSettingsPanel.tsx`
- [x] `client/src/api/types/projects.ts` — TypeScript types (Project, ProjectRole, ProjectSettings, etc.)
- [x] `client/src/api/tauri-adapter.ts` — Extend adapter with 11 project methods
- [x] `client/src/contexts/project-context.tsx` — React context for active project
- [x] `client/src/hooks/use-project.ts` — useProject, useProjects, useActiveProject hooks
- [x] `client/src/hooks/use-project-members.ts` — useProjectMembers hook
- [x] `client/src/App.tsx`Project-scoped routes + redirects for legacy URLs
- [x] `client/src/components/projects/ProjectSidebar.tsx`
- [x] `client/src/components/projects/ProjectSwitcher.tsx`
- [x] `client/src/components/projects/ProjectList.tsx`
- [x] `client/src/components/projects/ProjectSettingsPanel.tsx`
---

View File

@@ -1,36 +1,84 @@
# Sprint 19: Artifacts v1
> **Size**: XL | **Owner**: Backend | **Prerequisites**: Sprint 18
> **Size**: XL | **Owner**: Backend | **Prerequisites**: Sprint 18 (Projects)
> **Phase**: 5 - Platform Evolution
---
## Validation Status (2025-12-29)
## Open Issues & Prerequisites
### ✅ PREREQUISITES VERIFIED
> ✅ **Review Date**: 2025-12-31 — Prerequisites verified, design decisions finalized
| Asset | Status | Location |
|-------|--------|----------|
| SegmentModel.embedding | ✅ Verified | `models/core/meeting.py:202-205` with Vector(1536) |
| cosine_distance query | ✅ Verified | `repositories/segment_repo.py:148` in `search_semantic()` |
| Encrypted asset storage | ✅ Verified | `infrastructure/audio/writer.py` with AES-GCM |
| EMBEDDING_DIM = 1536 | ✅ Verified | `models/_base.py:8` |
### Blocking Issues
**Ready to implement**: Vector infrastructure and encryption patterns exist. Artifact-specific models and chunking can proceed.
| ID | Issue | Status | Resolution |
|----|-------|--------|------------|
| **B1** | **Sprint 18 (Projects) partial** | ✅ Resolved | Project scoping + repositories + UI implemented |
### Design Gaps to Address
| ID | Gap | Resolution |
|----|-----|------------|
| G1 | No EmbeddingProvider abstraction | Create protocol + OpenAI/SentenceTransformers implementations |
| G2 | No text chunking infrastructure | Create TextChunker with configurable strategies |
| G3 | Artifact storage location undefined | Use same pattern as audio: `~/.noteflow/artifacts/<artifact-id>/` |
### Prerequisite Verification
| Prerequisite | Status | Notes |
|--------------|--------|-------|
| SegmentModel.embedding (1536 dims) | ✅ Verified | `models/core/meeting.py:213` |
| cosine_distance query | ✅ Verified | `repositories/segment_repo.py:148` |
| Encrypted asset storage | ✅ Verified | `infrastructure/security/crypto.py` ChunkedAssetWriter |
| Project entity | ✅ Verified | `domain/entities/project.py:141-279` |
---
## Validation Status (2025-12-30)
### NOT IMPLEMENTED
| Component | Status | Notes |
|-----------|--------|-------|
| Artifact domain entity | Not implemented | `domain/entities/artifact.py` |
| ArtifactChunk entity | Not implemented | With embedding vector |
| EmbeddingProvider protocol | Not implemented | `domain/ports/embedding.py` |
| TextChunker service | Not implemented | `infrastructure/artifacts/chunking.py` |
| Text extractors | Not implemented | PDF, DOCX, HTML, EPUB, RTF, ODT, MD, TXT |
| ArtifactModel ORM | Not implemented | `infrastructure/persistence/models/artifact.py` |
| ArtifactRepository | Not implemented | Port + SQLAlchemy implementation |
| Artifact gRPC mixin | Not implemented | Upload, list, delete, retrieve RPCs |
### PARTIALLY IMPLEMENTED
| Component | Status | Notes |
|-----------|--------|-------|
| Encrypted storage | ✅ Exists | Reuse `ChunkedAssetWriter` from audio |
| Vector search | ✅ Exists | Extend `search_semantic` pattern |
| Project scoping | ✅ Implemented | Project entity + repositories + active project flow |
**Downstream impact**: Sprint 20 (Artifacts v2 + RAG), Sprint 24 (Graph + Explore)
**Shared abstractions for Sprint 20**: This sprint defines `EmbeddingProvider` and `VectorStorePort` protocols that Sprint 20 will implement with Qdrant. The pgvector implementation here serves as the initial backend; Qdrant migration happens in Sprint 20a.
---
## Objective
Get any non-meeting corpus into embeddings and retrievable context. Enable RAG over uploaded documents.
Enable uploading, processing, and semantic retrieval of non-meeting documents (PDFs, DOCX, etc.) to provide RAG context for Q&A. This creates a unified knowledge base combining meeting transcripts and uploaded artifacts.
---
## Decision: Per-Workspace
## Key Decisions
> **Decided**: Artifacts scoped per-workspace by default, can be linked to projects. Simpler ACL model, easier cross-project sharing.
> **Default**: If `project_id` is omitted, use the workspace's active project (default project if none set).
> **Roles**: Workspace OWNER/ADMIN implies ProjectRole.ADMIN for artifact access.
| Decision | Choice | Rationale |
|----------|--------|-----------|
| **Embedding Provider** | Protocol abstraction | Multiple implementations (OpenAI, SentenceTransformers) via `EmbeddingProvider` protocol |
| **Supported Formats** | Extended (8 types) | PDF, DOCX, MD, TXT, HTML, EPUB, RTF, ODT covers most enterprise documents |
| **Chunking Strategy** | Configurable | Per-artifact strategy selection: semantic, fixed-size, or paragraph-based |
| **Encryption** | Yes (at rest) | Reuse `ChunkedAssetWriter` pattern for consistent security posture |
| **Scope** | Per-workspace | Artifacts scoped to workspace, optionally linked to project |
| **Embedding Dimension** | 1536 | Match existing `EMBEDDING_DIM` constant |
---
@@ -38,37 +86,562 @@ Get any non-meeting corpus into embeddings and retrievable context. Enable RAG o
| Asset | Location | Implication |
|-------|----------|-------------|
| `SegmentModel.embedding` (1536 dims) | ORM | Vector infrastructure ready |
| `cosine_distance` similarity query | Repositories | Retrieval plumbing exists |
| Encrypted asset storage | `infrastructure/audio/` | Storage patterns exist |
| `EMBEDDING_DIM = 1536` | DB config | Dimension standardized |
| `SegmentModel.embedding` Vector(1536) | `models/core/meeting.py:213` | Dimension standardized, vector infra ready |
| `search_semantic()` cosine distance | `repositories/segment_repo.py:130-170` | Retrieval pattern to replicate |
| `ChunkedAssetWriter` / `ChunkedAssetReader` | `infrastructure/security/crypto.py:151-313` | Encrypted file storage pattern |
| `AesGcmCryptoBox` envelope encryption | `infrastructure/security/crypto.py:35-148` | DEK generation and wrapping |
| `Project` entity with `workspace_id` | `domain/entities/project.py:141-279` | Scoping infrastructure |
| `ProjectSettings.rag_enabled` | `domain/entities/project.py:79` | Per-project RAG toggle |
---
## Scope
| Task | Effort |
|------|--------|
| `ArtifactModel` (metadata, chunks) | M |
| `ArtifactChunkModel` with embedding | M |
| Project scoping (`project_id`, default routing) | S |
| Artifact repositories + ports | M |
| File upload endpoint | M |
| Text extraction (PDF, DOCX, MD) | L |
| Chunking pipeline | M |
| Embedding pipeline | M |
| RAG retrieval RPC (align with Sprint 20 `SearchProject`) | M |
| Artifact upload UI | M |
| Task | Effort | Notes |
|------|--------|-------|
| **Domain Layer** | | |
| `Artifact` entity + `ArtifactId` value object | M | Status, metadata, file info |
| `ArtifactChunk` entity with embedding | M | Chunk text, position, embedding vector |
| `ArtifactType` enum (8 file types) | S | PDF, DOCX, MD, TXT, HTML, EPUB, RTF, ODT |
| `ChunkingStrategy` enum | S | SEMANTIC, FIXED_SIZE, PARAGRAPH |
| `EmbeddingProvider` port | S | Protocol for embedding generation |
| **Infrastructure Layer** | | |
| `ArtifactModel` + `ArtifactChunkModel` ORM | M | With Vector(1536) column |
| `SqlAlchemyArtifactRepository` | M | CRUD + semantic search |
| `OpenAIEmbeddingProvider` | M | text-embedding-3-small |
| `SentenceTransformersEmbeddingProvider` | M | all-MiniLM-L6-v2 (optional) |
| Text extractors (8 file types) | L | Unified extractor interface |
| `TextChunker` with strategies | M | Semantic, fixed, paragraph chunkers |
| Encrypted artifact storage | S | Reuse `ChunkedAssetWriter` |
| **Application Layer** | | |
| `ArtifactService` | L | Upload, process, embed, retrieve |
| **API Layer** | | |
| Proto messages + RPCs (6 endpoints) | M | Upload, List, Get, Delete, Search, GetChunks |
| `ArtifactMixin` gRPC mixin | M | RPC implementations |
| **Client Layer** | | |
| Artifact upload UI | M | Drag-drop, progress, file list |
| Artifact list/search page | M | Grid view with filters |
| Alembic migration | S | artifacts + artifact_chunks tables |
**Total Effort**: XL (3-4 weeks)
---
## Domain Model
### Artifact Entity
```python
# src/noteflow/domain/entities/artifact.py
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import NewType
from uuid import UUID, uuid4
from noteflow.domain.utils.time import utc_now
ArtifactId = NewType("ArtifactId", UUID)
class ArtifactType(Enum):
"""Supported artifact file types."""
PDF = "pdf"
DOCX = "docx"
MARKDOWN = "md"
TEXT = "txt"
HTML = "html"
EPUB = "epub"
RTF = "rtf"
ODT = "odt"
@classmethod
def from_extension(cls, ext: str) -> ArtifactType:
"""Get artifact type from file extension."""
ext = ext.lower().lstrip(".")
mapping = {
"pdf": cls.PDF,
"docx": cls.DOCX,
"md": cls.MARKDOWN,
"markdown": cls.MARKDOWN,
"txt": cls.TEXT,
"text": cls.TEXT,
"html": cls.HTML,
"htm": cls.HTML,
"epub": cls.EPUB,
"rtf": cls.RTF,
"odt": cls.ODT,
}
if ext not in mapping:
raise ValueError(f"Unsupported file extension: {ext}")
return mapping[ext]
class ArtifactStatus(Enum):
"""Processing status for artifacts."""
PENDING = "pending" # Uploaded, awaiting processing
PROCESSING = "processing" # Extraction/chunking/embedding in progress
READY = "ready" # Fully processed and searchable
FAILED = "failed" # Processing failed
class ChunkingStrategy(Enum):
"""Text chunking strategies."""
SEMANTIC = "semantic" # Sentence-based with overlap, respects paragraphs
FIXED_SIZE = "fixed_size" # Token-based fixed windows
PARAGRAPH = "paragraph" # One chunk per paragraph
@dataclass
class ChunkingConfig:
"""Configuration for text chunking."""
strategy: ChunkingStrategy = ChunkingStrategy.SEMANTIC
max_chunk_size: int = 512 # Max tokens per chunk
overlap: int = 128 # Token overlap between chunks
min_chunk_size: int = 50 # Minimum tokens to form a chunk
@dataclass
class Artifact:
"""A non-meeting document uploaded for RAG retrieval.
Artifacts are scoped to a workspace and optionally linked to a project.
They are processed into chunks with embeddings for semantic search.
"""
id: ArtifactId
workspace_id: UUID
name: str
artifact_type: ArtifactType
file_size_bytes: int
status: ArtifactStatus = ArtifactStatus.PENDING
project_id: UUID | None = None
description: str | None = None
original_filename: str | None = None
asset_path: str | None = None # Relative path to encrypted file
chunk_count: int = 0
chunking_config: ChunkingConfig = field(default_factory=ChunkingConfig)
error_message: str | None = None
created_at: datetime = field(default_factory=utc_now)
updated_at: datetime = field(default_factory=utc_now)
processed_at: datetime | None = None
created_by: UUID | None = None
@staticmethod
def create(
workspace_id: UUID,
name: str,
artifact_type: ArtifactType,
file_size_bytes: int,
*,
project_id: UUID | None = None,
description: str | None = None,
original_filename: str | None = None,
chunking_config: ChunkingConfig | None = None,
created_by: UUID | None = None,
) -> Artifact:
"""Create a new artifact in pending status."""
artifact_id = ArtifactId(uuid4())
return Artifact(
id=artifact_id,
workspace_id=workspace_id,
name=name,
artifact_type=artifact_type,
file_size_bytes=file_size_bytes,
project_id=project_id,
description=description,
original_filename=original_filename,
asset_path=str(artifact_id), # Default to artifact ID
chunking_config=chunking_config or ChunkingConfig(),
created_by=created_by,
)
def mark_processing(self) -> None:
"""Mark artifact as processing."""
self.status = ArtifactStatus.PROCESSING
self.updated_at = utc_now()
def mark_ready(self, chunk_count: int) -> None:
"""Mark artifact as ready after successful processing."""
self.status = ArtifactStatus.READY
self.chunk_count = chunk_count
self.processed_at = utc_now()
self.updated_at = utc_now()
def mark_failed(self, error: str) -> None:
"""Mark artifact as failed with error message."""
self.status = ArtifactStatus.FAILED
self.error_message = error
self.updated_at = utc_now()
@property
def is_searchable(self) -> bool:
"""Check if artifact is ready for semantic search."""
return self.status == ArtifactStatus.READY and self.chunk_count > 0
```
### ArtifactChunk Entity
```python
# src/noteflow/domain/entities/artifact.py (continued)
from typing import NewType
ArtifactChunkId = NewType("ArtifactChunkId", UUID)
@dataclass
class ArtifactChunk:
"""A chunk of text from an artifact with embedding.
Chunks are the atomic units for semantic search. Each chunk
contains text, position metadata, and an embedding vector.
"""
id: ArtifactChunkId
artifact_id: ArtifactId
text: str
chunk_index: int # Position in document (0-based)
start_char: int # Character offset in source text
end_char: int # End character offset
token_count: int
embedding: list[float] | None = None # 1536-dim vector
metadata: dict[str, str] = field(default_factory=dict) # Page number, section, etc.
created_at: datetime = field(default_factory=utc_now)
@staticmethod
def create(
artifact_id: ArtifactId,
text: str,
chunk_index: int,
start_char: int,
end_char: int,
token_count: int,
*,
metadata: dict[str, str] | None = None,
) -> ArtifactChunk:
"""Create a new chunk without embedding (added during processing)."""
return ArtifactChunk(
id=ArtifactChunkId(uuid4()),
artifact_id=artifact_id,
text=text,
chunk_index=chunk_index,
start_char=start_char,
end_char=end_char,
token_count=token_count,
metadata=metadata or {},
)
```
### EmbeddingProvider Port
```python
# src/noteflow/domain/ports/embedding.py
from typing import Protocol
class EmbeddingProvider(Protocol):
"""Protocol for generating text embeddings."""
@property
def dimension(self) -> int:
"""Embedding vector dimension."""
...
@property
def model_name(self) -> str:
"""Name of the embedding model."""
...
async def embed_text(self, text: str) -> list[float]:
"""Generate embedding for a single text.
Args:
text: Text to embed.
Returns:
Embedding vector of `dimension` floats.
"""
...
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
"""Generate embeddings for multiple texts.
Args:
texts: List of texts to embed.
Returns:
List of embedding vectors.
"""
...
```
---
## API Schema
### Proto Additions (gRPC)
```protobuf
// noteflow.proto additions
enum ArtifactType {
ARTIFACT_TYPE_UNSPECIFIED = 0;
ARTIFACT_TYPE_PDF = 1;
ARTIFACT_TYPE_DOCX = 2;
ARTIFACT_TYPE_MARKDOWN = 3;
ARTIFACT_TYPE_TEXT = 4;
ARTIFACT_TYPE_HTML = 5;
ARTIFACT_TYPE_EPUB = 6;
ARTIFACT_TYPE_RTF = 7;
ARTIFACT_TYPE_ODT = 8;
}
enum ArtifactStatus {
ARTIFACT_STATUS_UNSPECIFIED = 0;
ARTIFACT_STATUS_PENDING = 1;
ARTIFACT_STATUS_PROCESSING = 2;
ARTIFACT_STATUS_READY = 3;
ARTIFACT_STATUS_FAILED = 4;
}
enum ChunkingStrategy {
CHUNKING_STRATEGY_UNSPECIFIED = 0;
CHUNKING_STRATEGY_SEMANTIC = 1;
CHUNKING_STRATEGY_FIXED_SIZE = 2;
CHUNKING_STRATEGY_PARAGRAPH = 3;
}
message ChunkingConfig {
ChunkingStrategy strategy = 1;
int32 max_chunk_size = 2; // Max tokens per chunk
int32 overlap = 3; // Token overlap
int32 min_chunk_size = 4; // Minimum tokens
}
message Artifact {
string id = 1;
string workspace_id = 2;
string project_id = 3; // Optional
string name = 4;
ArtifactType artifact_type = 5;
ArtifactStatus status = 6;
int64 file_size_bytes = 7;
int32 chunk_count = 8;
string description = 9;
string original_filename = 10;
string error_message = 11;
ChunkingConfig chunking_config = 12;
google.protobuf.Timestamp created_at = 13;
google.protobuf.Timestamp updated_at = 14;
google.protobuf.Timestamp processed_at = 15;
}
message ArtifactChunk {
string id = 1;
string artifact_id = 2;
string text = 3;
int32 chunk_index = 4;
int32 start_char = 5;
int32 end_char = 6;
int32 token_count = 7;
map<string, string> metadata = 8;
}
// Upload artifact (streaming for large files)
message UploadArtifactRequest {
oneof content {
UploadArtifactMetadata metadata = 1;
bytes chunk = 2;
}
}
message UploadArtifactMetadata {
string workspace_id = 1;
string project_id = 2; // Optional
string name = 3;
string filename = 4;
string description = 5;
ChunkingConfig chunking_config = 6;
}
message UploadArtifactResponse {
Artifact artifact = 1;
}
// List artifacts
message ListArtifactsRequest {
string workspace_id = 1;
string project_id = 2; // Optional filter
ArtifactStatus status = 3; // Optional filter
int32 limit = 4;
int32 offset = 5;
}
message ListArtifactsResponse {
repeated Artifact artifacts = 1;
int32 total = 2;
}
// Get artifact
message GetArtifactRequest {
string artifact_id = 1;
}
message GetArtifactResponse {
Artifact artifact = 1;
}
// Delete artifact
message DeleteArtifactRequest {
string artifact_id = 1;
}
message DeleteArtifactResponse {
bool deleted = 1;
}
// Search artifacts (semantic)
message SearchArtifactsRequest {
string workspace_id = 1;
string project_id = 2; // Optional filter
string query = 3;
int32 limit = 4; // Default 10
}
message SearchResult {
ArtifactChunk chunk = 1;
Artifact artifact = 2;
float similarity_score = 3;
}
message SearchArtifactsResponse {
repeated SearchResult results = 1;
}
// Get chunks for an artifact
message GetArtifactChunksRequest {
string artifact_id = 1;
int32 limit = 2;
int32 offset = 3;
}
message GetArtifactChunksResponse {
repeated ArtifactChunk chunks = 1;
int32 total = 2;
}
// Service additions
service NoteFlowService {
// ... existing RPCs ...
// Artifact management
rpc UploadArtifact(stream UploadArtifactRequest) returns (UploadArtifactResponse);
rpc ListArtifacts(ListArtifactsRequest) returns (ListArtifactsResponse);
rpc GetArtifact(GetArtifactRequest) returns (GetArtifactResponse);
rpc DeleteArtifact(DeleteArtifactRequest) returns (DeleteArtifactResponse);
rpc SearchArtifacts(SearchArtifactsRequest) returns (SearchArtifactsResponse);
rpc GetArtifactChunks(GetArtifactChunksRequest) returns (GetArtifactChunksResponse);
}
```
---
## Migration Strategy
### Phase 1: Schema (Week 1)
1. Create Alembic migration for `artifacts` and `artifact_chunks` tables
2. Add `project_id` FK to `artifacts` (nullable)
3. Add `embedding` Vector(1536) column to `artifact_chunks`
4. Create indexes for workspace_id, project_id, status
### Phase 2: Infrastructure (Week 2)
1. Implement text extractors for all 8 file types
2. Implement `TextChunker` with configurable strategies
3. Implement `OpenAIEmbeddingProvider`
4. Implement `SqlAlchemyArtifactRepository`
### Phase 3: Service + API (Week 3)
1. Implement `ArtifactService` with upload/process pipeline
2. Add gRPC mixin with 6 RPCs
3. Wire up to `NoteFlowServicer`
### Phase 4: Client (Week 4)
1. Implement artifact upload UI
2. Implement artifact list/search page
3. Integration testing
### Migration Risks
| Risk | Mitigation |
|------|------------|
| Large file processing timeouts | Background processing with status polling |
| Embedding API rate limits | Batch embedding with exponential backoff |
| Vector dimension mismatch | Validate dimension matches EMBEDDING_DIM on insert |
---
## Shared Types & Reuse Notes
- **Encrypted storage**: Reuse `ChunkedAssetWriter`/`ChunkedAssetReader` from `infrastructure/security/crypto.py`
- **Vector search**: Extend `search_semantic` pattern from `SegmentRepository`
- **Repository base**: Extend `_BaseRepository` from `infrastructure/persistence/repositories/_base.py`
- **ORM converter**: Add `ArtifactConverter` following `OrmConverter` pattern
- **Align with Sprint 20**: Use compatible `SearchResult` schema for RAG responses
---
## Deliverables
- `src/noteflow/domain/entities/artifact.py`
- `src/noteflow/infrastructure/artifacts/` — Storage, chunking, embedding
- Artifact repositories + ports (domain + infrastructure)
- RAG retrieval RPC (align response schema with Sprint 20 SearchProject)
- `client/src/pages/Artifacts.tsx`
### Backend
**Domain Layer**:
- [ ] `src/noteflow/domain/entities/artifact.py` — Artifact, ArtifactChunk, ArtifactType, ChunkingStrategy
- [ ] `src/noteflow/domain/ports/embedding.py` — EmbeddingProvider protocol
- [ ] `src/noteflow/domain/ports/vector_store.py` — VectorStorePort protocol (shared with Sprint 20)
- [ ] `src/noteflow/domain/ports/repositories/artifact.py` — ArtifactRepository, ArtifactChunkRepository protocols
**Infrastructure Layer**:
- [ ] `src/noteflow/infrastructure/persistence/models/artifact.py` — ArtifactModel, ArtifactChunkModel
- [ ] `src/noteflow/infrastructure/persistence/repositories/artifact_repo.py` — SQLAlchemy implementations
- [ ] `src/noteflow/infrastructure/artifacts/extractors/` — PDF, DOCX, HTML, EPUB, RTF, ODT, MD, TXT extractors
- [ ] `src/noteflow/infrastructure/artifacts/chunking.py` — TextChunker with strategies
- [ ] `src/noteflow/infrastructure/artifacts/storage.py` — Encrypted artifact file storage
- [ ] `src/noteflow/infrastructure/embedding/openai_provider.py` — OpenAI embedding provider
- [ ] `src/noteflow/infrastructure/embedding/sentence_transformers_provider.py` — Local embedding provider
- [ ] `src/noteflow/infrastructure/converters/artifact_converters.py` — ORM ↔ domain converters
**Application Layer**:
- [ ] `src/noteflow/application/services/artifact_service.py` — Upload, process, embed, retrieve
**API Layer**:
- [ ] `src/noteflow/grpc/proto/noteflow.proto` — Artifact messages and RPCs
- [ ] `src/noteflow/grpc/_mixins/artifact.py` — gRPC mixin with 6 RPCs
**Migrations**:
- [ ] `src/noteflow/infrastructure/persistence/migrations/versions/xxx_add_artifacts_schema.py`
### Client
- [ ] `client/src/api/types/artifacts.ts` — TypeScript artifact types
- [ ] `client/src/hooks/use-artifacts.ts` — Artifact operations hook
- [ ] `client/src/pages/Artifacts.tsx` — Artifact list and search page
- [ ] `client/src/components/artifacts/ArtifactUpload.tsx` — Drag-drop upload component
- [ ] `client/src/components/artifacts/ArtifactCard.tsx` — Artifact display card
- [ ] `client/src-tauri/src/commands/artifacts.rs` — Tauri IPC commands
---
@@ -76,39 +649,50 @@ Get any non-meeting corpus into embeddings and retrievable context. Enable RAG o
### Fixtures to extend or create
- `tests/conftest.py`: add `workspace_id`, `project_id`, `user_id`, and `artifact_storage_dir` fixtures; extend `mock_uow` with `artifacts` + `artifact_chunks` repos.
- `tests/grpc/conftest.py`: add `mock_artifact_repo` and `mock_artifact_chunk_repo` to keep gRPC tests isolated.
- New `tests/infrastructure/artifacts/conftest.py`: provide `sample_artifact_file_*` (pdf/docx/md/txt), `artifact_content`, `chunking_service`, and `embedding_provider_stub`.
- New `tests/fixtures/artifacts/`: add small representative files for extraction (ASCII-safe if possible).
- `tests/conftest.py`: add `artifact_factory`, `artifact_chunk_factory`, `mock_embedding_provider`
- `tests/infrastructure/artifacts/conftest.py`: add `sample_pdf`, `sample_docx`, `sample_md`, `sample_txt`, `sample_html`, `sample_epub`, `sample_rtf`, `sample_odt`
- `tests/fixtures/artifacts/`: add small representative files for each format (< 10KB each)
### Parameterized tests
- Extraction per file type: PDF, DOCX, Markdown, TXT.
- Chunking strategies and sizes: sentence/paragraph, `max_chunk_size`, `overlap`.
- Project scoping: explicit `project_id` vs default active project.
- Retrieval limits + filters: `limit`, `chunk_type`, `meeting_id`, `artifact_id`.
- **Extractors**: parameterize by file type (8 types)
- **Chunking**: parameterize by strategy (SEMANTIC, FIXED_SIZE, PARAGRAPH)
- **Search**: parameterize by limit, filters (workspace, project, status)
- **Upload**: parameterize by file size (small, medium, large)
### Core test cases (behavior + robustness)
### Core test cases
- **Domain**: Artifact creation validates required metadata, workspace/project scoping, and default project routing.
- **Storage**: Uploaded binary is encrypted at rest; decrypt matches input; tampered ciphertext fails cleanly.
- **Extraction**: Handles empty content, corrupted files, non-UTF8, and oversized documents with actionable errors.
- **Chunking**: Deterministic chunk IDs, overlap rules enforced, and stable output ordering.
- **Embedding**: Embedding called once per chunk batch; dimension mismatches surface as clear errors.
- **Retrieval RPC**: Project scoping enforced; default project applied when `project_id` omitted; returns ordered results.
- **Domain**: Artifact creation validates required fields; status transitions are valid; ChunkingConfig defaults
- **Extractors**: Each extractor handles empty content, corrupted files, non-UTF8, oversized documents
- **Chunking**: Deterministic chunk ordering; overlap rules enforced; respects min/max size
- **Embedding**: Provider called once per batch; dimension validated; rate limit handling
- **Repository**: CRUD operations; semantic search returns ordered results; scope filtering works
- **Service**: End-to-end upload → process → search flow; status updates correctly; errors propagate
- **gRPC**: Streaming upload works; list pagination correct; delete removes file and chunks
---
## Shared Types & Reuse Notes
## Quality Gates
- Reuse encrypted asset storage patterns from `infrastructure/audio/` for artifact binary storage.
- Reuse vector embedding dimension constant and chunking utilities across meeting + artifact pipelines.
- Align RAG response types with Sprint 20 (`SearchResult`, `SearchFilters`) to avoid API churn.
- [ ] `pytest tests/domain/test_artifact.py` passes
- [ ] `pytest tests/infrastructure/artifacts/` passes
- [ ] `pytest tests/application/test_artifact_service.py` passes
- [ ] `pytest tests/grpc/test_artifact_mixin.py` passes
- [ ] `pytest tests/quality/` passes (23+ test smell checks)
- [ ] `ruff check src/noteflow` zero errors
- [ ] `basedpyright` zero type errors
- [ ] `npm run lint` zero frontend errors
- [ ] `npm run test` frontend tests pass
- [ ] No `# type: ignore` without justification
- [ ] All public functions have docstrings
---
## Post-Sprint
- Artifact versioning
- Format-specific extractors
- Chunk overlap tuning
- [ ] Artifact versioning (track revisions)
- [ ] Format-specific extractors with richer metadata (PDF page numbers, DOCX styles)
- [ ] Chunk overlap tuning based on retrieval quality metrics
- [ ] Progress webhooks for long uploads
- [ ] Quota enforcement per workspace
- [ ] Integrate with Sprint 20 Q&A interface

View File

@@ -5,13 +5,13 @@
---
## Validation Status (2025-12-29)
## Validation Status (2025-12-31)
### 🚫 BLOCKED — Prerequisite Not Implemented
### ✅ Unblocked — Prerequisite Implemented
| Prerequisite | Status | Impact |
|--------------|--------|--------|
| Project scoping (Sprint 18) | ❌ Not implemented | Cannot scope MCP servers to projects |
| Project scoping (Sprint 18) | ✅ Implemented | MCP servers can now be scoped to projects |
| Component | Status | Notes |
|-----------|--------|-------|
@@ -20,7 +20,7 @@
| MCP RPCs | ❌ Not implemented | No proto messages for MCP config |
| MCP UI | ❌ Not implemented | No Settings integration |
**Action required**: Complete Sprint 18 (Projects) before starting.
**Action required**: Sprint 18 prerequisite satisfied; proceed with MCP implementation.
**Downstream impact**: Sprint 22 (Rules) and Sprint 25 (LangGraph) depend on MCP configuration.
@@ -44,7 +44,7 @@ Centralize "where context and tools come from" with scoped MCP server configurat
|-------|----------|-------------|
| Settings infrastructure | `config/settings.py` | Feature toggle patterns |
| Workspace scoping (Sprint 16) | `persistence/models/identity/identity.py` | ✅ Scope levels available |
| Project scoping (Sprint 18) | ❌ Not implemented | **Prerequisite incomplete** |
| Project scoping (Sprint 18) | ✅ Implemented | **Prerequisite satisfied** |
---

View File

@@ -5,7 +5,7 @@
---
## Validation Status (2025-12-29)
## Validation Status (2025-12-31)
### ❌ NOT IMPLEMENTED
@@ -17,7 +17,7 @@
| Rules RPCs | ❌ Not implemented | No proto messages for rules |
| Rules UI | ❌ Not implemented | `client/src/pages/Rules.tsx` does not exist |
**Blockers**: Sprint 21 (MCP Config) prerequisite is incomplete (Project scoping missing).
**Blockers**: Sprint 21 (MCP Config) prerequisite is incomplete.
**Downstream impact**: Sprint 23 (Analytics) depends on Rules schema for auditing.

View File

@@ -5,13 +5,13 @@
---
## Validation Status (2025-12-29)
## Validation Status (2025-12-31)
### ⚠️ PARTIAL PREREQUISITES
| Prerequisite | Status | Notes |
|--------------|--------|-------|
| Sprint 18 (Projects) | ❌ Not implemented | Project scoping missing |
| Sprint 18 (Projects) | ✅ Implemented | Project scoping available |
| Sprint 19 (Artifacts) | ❌ Not implemented | Artifact infrastructure missing |
| Asset | Status | Location |

View File

@@ -5,7 +5,7 @@
---
## Validation Status (2025-12-29)
## Validation Status (2025-12-31)
### 🚫 BLOCKED — Prerequisites Not Implemented
@@ -13,9 +13,9 @@
|--------------|--------|--------|
| MCP configuration (Sprint 21) | ❌ Not implemented | Cannot configure tool sources |
| Usage events (Sprint 15 / OTel) | ❌ Not implemented | Cannot emit run metadata |
| Project scoping (Sprint 18) | ❌ Not implemented | Blocks Sprint 21 |
| Project scoping (Sprint 18) | ✅ Implemented | No longer blocks Sprint 21 |
**Action required**: Complete Sprint 18 (Projects), Sprint 21 (MCP Config), and Sprint 15 (Usage Events) before starting.
**Action required**: Complete Sprint 21 (MCP Config) and Sprint 15 (Usage Events) before starting.
---