Skip to content

Feature/process engine - #6

Merged
vybe merged 34 commits into
mainfrom
feature/process-engine
Jan 17, 2026
Merged

Feature/process engine#6
vybe merged 34 commits into
mainfrom
feature/process-engine

Conversation

@oleksandr-korin

@oleksandr-korin oleksandr-korin commented Jan 16, 2026

Copy link
Copy Markdown
Contributor

Note

Introduces example workflows and foundational docs for the process-driven platform.

  • Adds templates: content-review, customer-support, data-analysis with definition.yaml and template.yaml under config/process-templates/
  • Documents the platform: backlogs (MVP/Core/Advanced), index, development process, and architecture iterations (IT1IT4) in docs/PROCESS_DRIVEN_PLATFORM/
  • Updates docker-compose.yml to set backend TRINITY_DB_PATH=/data/trinity.db
  • Adjusts .gitignore to include src/backend/services/process_engine/repositories/

Written by Cursor Bugbot for commit aa3d56e. Configure here.

- Move PROCESS_DRIVEN_AGENTS.md to PROCESS_DRIVEN_PLATFORM/ folder
- Add IT1-IT4 thinking documents (analysis, architecture, DDD, UI/UX)
- Add phase-based backlog files (MVP, Core, Advanced)
- Add BACKLOG_INDEX.md with conventions and traceability
- Add DEVELOPMENT_PROCESS.md with workflow and testing strategy

This establishes the foundation for implementing the Process Engine feature.
- ProcessId: UUID wrapper with validation and generation
- ExecutionId: UUID wrapper for execution instances
- StepId: Validated string identifier (alphanumeric, hyphens, underscores)
- Version: Major.minor versioning with parsing and comparison
- Duration: Time duration with parsing ("30s", "5m", "2h", "1d")
- Money: Decimal-based currency representation

All value objects are frozen dataclasses for immutability.
Includes 67 unit tests covering all validation and operations.

Refs: IT3 Section 4.3 (Value Objects)
Domain Model:
- ProcessDefinition: Aggregate root with validation, publish/archive lifecycle
- StepDefinition: Entity for individual steps (agent_task, human_approval, gateway)
- StepExecution: Runtime state tracking for step instances
- OutputConfig: Process output configuration

Step Configurations:
- AgentTaskConfig: agent, message, timeout, model, temperature
- HumanApprovalConfig: title, description, assignees, timeout (stub)
- GatewayConfig: gateway_type, routes, default_route (stub)
- TimerConfig, NotificationConfig: stubs for Advanced phase

Validation:
- Duplicate step ID detection
- Invalid dependency reference checking
- Circular dependency detection via DFS
- Empty name/steps validation

Schema & Documentation:
- JSON Schema for editor validation (process-definition.schema.json)
- Example YAML files (content-pipeline.yaml, approval-workflow.yaml)

Includes 27 new tests (94 total process engine tests passing)

Refs: IT3 Section 4 (Aggregates)
Repository Interfaces:
- ProcessDefinitionRepository: Full CRUD + versioning + filtering
- ProcessExecutionRepository: Interface for execution state (impl later)

SQLite Implementation:
- SqliteProcessDefinitionRepository with JSON storage
- Indexed columns for name, status, and name+version uniqueness
- In-memory support for testing

CRUD Operations:
- save: Create or update (upsert by ID)
- get_by_id, get_by_name, get_latest_version
- list_all, list_by_name with filtering and pagination
- delete, exists, count

Version Tracking:
- Multiple versions per process name
- Version major.minor stored separately for efficient queries
- get_latest_version returns latest published

Also: Updated .gitignore to allow process engine repositories folder

Includes 22 new tests (116 total process engine tests passing)

Refs: IT3 Section 7 (Repositories)
ProcessValidator Service:
- validate_yaml(): Full validation pipeline from raw YAML
- validate_definition(): Validate existing ProcessDefinition

Validation Levels:
1. YAML syntax validation with line numbers
2. Schema validation (required fields, types)
3. Semantic validation (from domain layer):
   - No duplicate step IDs
   - All depends_on references exist
   - No circular dependencies
4. Agent existence checking (warnings only)

ValidationResult:
- Separates errors (blocking) from warnings (advisory)
- is_valid: True if no errors
- to_dict(): API-friendly response format

Error/Warning Details:
- message: Human-readable description
- level: error | warning
- path: JSON path (e.g., "steps[0].agent")
- line: YAML line number (when available)
- suggestion: Fix suggestion

Includes 26 new tests (142 total process engine tests passing)

Refs: IT3 Section 6 (Domain Services)
Domain Events:
- DomainEvent: Base class with kw_only timestamp for inheritance
- Process events: ProcessStarted, ProcessCompleted, ProcessFailed, ProcessCancelled
- Step events: StepStarted, StepCompleted, StepFailed, StepSkipped
- Approval events: ApprovalRequested, ApprovalDecided
- All events immutable (frozen dataclasses) with to_dict() serialization

Event Bus Infrastructure:
- EventBus: Abstract interface for publish/subscribe
- InMemoryEventBus: MVP implementation with async dispatch
- subscribe(): Handler for specific event types
- subscribe_all(): Global handler for all events
- unsubscribe(), clear(): Handler management
- Async non-blocking dispatch via asyncio.create_task()
- Error isolation: handler errors logged but don't stop other handlers
- wait_for_pending(): For testing and graceful shutdown

Includes 23 new tests (165 total process engine tests passing)

Refs: IT3 Section 5 (Domain Events)
Added comprehensive tests for execution-side domain model:
- ProcessExecution creation and initialization
- State transitions (start, complete, fail, cancel, pause, resume)
- Step operations (start_step, complete_step, fail_step)
- Query methods (get_completed_step_ids, all_steps_completed)
- Serialization roundtrip (to_dict, from_dict)
- StepExecution entity tests

Fixes gap identified during Sprint 1 review.
31 new tests (196 total process engine tests passing)
Implements REST API for process definitions:
- POST /api/processes - Create new process definition
- GET /api/processes - List all (with filters, pagination)
- GET /api/processes/{id} - Get single definition
- PUT /api/processes/{id} - Update draft definition
- DELETE /api/processes/{id} - Delete draft/archived
- POST /api/processes/{id}/validate - Validate existing
- POST /api/processes/validate - Validate YAML
- POST /api/processes/{id}/publish - Publish draft
- POST /api/processes/{id}/archive - Archive process
- POST /api/processes/{id}/new-version - Create new version

Also marks E2-01 (Execution State Model) as done - was
implemented in Sprint 1 with ProcessExecution aggregate.

21 new API tests (217 total process engine tests passing)
Implements SQLite repository for process executions:
- SqliteProcessExecutionRepository with save/get/delete/list operations
- Tables: process_executions, step_executions
- Proper serialization of Money (as cents), timestamps, JSON data
- Query methods: list_by_process, list_active, list_all with filters

Also adds input/cost fields to StepExecution entity for tracking
step-level inputs and costs (prepares for E2-06 output storage).

21 new repository tests (238 total process engine tests passing)
Implements centralized output storage management:
- OutputStorage service for store/retrieve/delete operations
- OutputPath value object with path pattern /executions/{id}/steps/{step}/output
- Unified API for accessing step outputs regardless of backend
- get_all_outputs() for bulk retrieval
- clear_execution_outputs() for cleanup
- Handles empty dict vs None distinction properly

23 new output storage tests (261 total process engine tests passing)

Sprint 2 complete:
- E1-04: Process Definition API ✓
- E2-01: Execution State Model ✓
- E2-02: Execution Repository ✓
- E2-06: Step Output Storage ✓
Implements the core execution engine for process orchestration:

ExecutionEngine:
- start() - Start new execution from definition
- resume() - Resume paused/partial execution
- cancel() - Cancel running execution
- Timeout handling per step with asyncio.wait_for
- Domain event emission (ProcessStarted, StepCompleted, etc.)

DependencyResolver:
- get_ready_steps() - Find steps with satisfied dependencies
- get_next_step() - Get next step for sequential execution
- get_execution_order() - Topological sort of all steps
- is_complete() / has_failed_steps() - Status checks

StepHandler Interface:
- Abstract base for step type handlers (AgentTask, etc.)
- StepHandlerRegistry for handler lookup
- StepContext for passing execution state
- StepResult for success/failure responses

16 new tests (277 total process engine tests passing)
Implements handler for agent_task step type:

AgentTaskHandler:
- Executes agent_task steps by sending messages to Trinity agents
- Variable substitution for {{input.X}} and {{steps.X.output}}
- Timeout handling from step config
- Cost extraction from agent response

AgentGateway (Anti-Corruption Layer):
- Wraps Trinity's AgentClient for clean process engine integration
- Agent availability checking via Docker container status
- Message sending with context metadata
- Error handling and wrapping

10 new tests (287 total process engine tests passing)
Implements template expression evaluation for process messages:

ExpressionEvaluator:
- Evaluates {{expression}} placeholders in strings
- Supports input.X, input.X.Y for nested input data
- Supports steps.X.output, steps.X.output.Y for step outputs
- Supports execution.id, process.name context
- Strict mode raises ExpressionError for undefined expressions
- Expression extraction and validation utilities

EvaluationContext:
- Path-based access to input_data, step_outputs, metadata
- Handles nested dict navigation

AgentTaskHandler Integration:
- Now uses ExpressionEvaluator for variable substitution
- Supports all expression types in message and agent name

30 new tests (317 total process engine tests passing)
Implements REST API for managing process executions:

Endpoints:
- POST /api/processes/{id}/execute - Start new execution
- GET /api/executions - List executions with filters
- GET /api/executions/{id} - Get execution detail
- POST /api/executions/{id}/cancel - Cancel running execution
- POST /api/executions/{id}/retry - Retry failed execution
- GET /api/executions/{id}/steps/{step_id}/output - Get step output

Features:
- Background task execution via BackgroundTasks
- Status filtering (pending, running, completed, failed)
- Process ID filtering
- Pagination support (limit/offset)
- Full step execution details in response
- Authentication required for all endpoints
- Auto-generated OpenAPI docs

13 new tests (330 total process engine tests passing)
Sprint 4: Definition UI (Frontend)

E3-01: Process List View
- Card grid showing all processes with status badges
- Sorting: newest, oldest, name, status
- Filtering by status (draft/published/archived)
- Actions: Execute, Edit, Delete
- Empty state with Create CTA

E3-02: YAML Editor Component
- Monaco editor integration with dynamic import
- YAML syntax highlighting
- Line numbers and word wrap
- Dark mode theme support
- Inline validation error markers
- Copy to clipboard, Cmd+S to save

E3-04: Process Editor Page
- Full editor with validation panel
- Save/Publish/Execute buttons
- Unsaved changes warning on navigation
- Default YAML template for new processes
- Keyboard shortcut (Cmd+S)

Also:
- Added processes Pinia store
- Added routes for /processes, /processes/new, /processes/:id
- Added Processes link to NavBar
- Added monaco-editor dependency
Sprint 5: Monitoring UI (Frontend)

E4-01: Execution List View
- Table with status icons (✅ ❌ 🔄 ⏳)
- Columns: Process Name, Status, Started, Duration, Cost
- Filters by status and process
- Auto-refresh (30s polling) with pause/resume
- Pagination support
- Cancel/Retry actions

E4-02: Execution Timeline View
- Step-by-step progress display
- Progress bar with completion percentage
- Duration bars relative to longest step
- Status indicators (running animation)
- Click to expand step details

E4-03: Step Detail Panel
- Expandable within timeline
- Timing info: started, completed, duration, retries
- Error display with copy button
- Output loading on demand
- Copy output to clipboard

E4-05: Process Dashboard
- Overall stats: processes, executions, success rate, cost
- Recent executions list
- Published processes with quick execute
- Quick action buttons

Also:
- Added executions Pinia store
- Added routes for /executions, /executions/:id, /process-dashboard
- Added Executions link to NavBar
Fixes:
- Updated deploy scripts to use `docker compose` (V2) instead of legacy `docker-compose`
- Fixed frontend build error by adding `js-yaml` dependency
- Fixed backend crash in `executions.py` due to double `Depends` injection
- Fixed `sqlite3.OperationalError` in `processes.py` by ensuring DB directory exists
- Fixed 404 execution error by aligning DB paths between processes and executions routers
- Fixed `AttributeError` in `processes.py` by using `.major` for version logging
- Fixed UI "Delete" and "Unsaved Changes" modals by passing `:visible="true"` to ConfirmDialog
- Added `TRINITY_DB_PATH` env var to backend service for correct persistence

Features:
- Implemented `EventLogger` service (E15-04) to persist domain events
- Added `SqliteEventRepository` implementation
- Added `Event History` tab to Execution Detail UI
- Completed Process Dashboard implementation (E4-05)

Refs: BACKLOG_MVP.md
…or debugging UI

Sprint 7 implements comprehensive error handling for process execution:

- Add RetryPolicy value object with configurable max_attempts, initial_delay, backoff_multiplier
- Add ErrorPolicy value object with on_error actions (fail_process, skip_step)
- Implement retry logic in ExecutionEngine with exponential backoff
- Fix error code propagation through fail_step chain
- Update API schema to return full error objects (code, message, retry_count)
- Enhance ExecutionTimeline UI to display error details, retry attempts
- Fix WebSocket handler to properly construct error objects from events
- Update dependency resolver to handle skipped steps correctly
- Add comprehensive unit tests for error handling scenarios

Stories completed: E13-01, E13-02, E13-04
Sprint 8 implements parallel step execution for faster process completion:

- Add ParallelGroup and ParallelStructure classes for parallel analysis
- DependencyResolver.get_parallel_structure() identifies parallelizable steps
- ExecutionEngine runs independent steps concurrently with asyncio.gather()
- Add ExecutionConfig.parallel_execution and max_concurrent_steps options
- API returns parallel_level for each step and has_parallel_steps flag
- UI shows parallel indicator (⫘) for steps at same execution level
- Add comprehensive unit tests for parallel detection and execution

Stories completed: E5-01, E5-02, E5-03
- ProcessFlowPreview: Vertical orientation by default with swimlane layout
- ProcessFlowPreview: Parallel steps grouped horizontally with dashed border
- ProcessFlowPreview: Compact design to fit without scrolling
- ExecutionTimeline: Parallel indicator (⫘) and level-based sorting
…ox UI

Sprint 9 - Human Approval:
- Add human_approval step type that pauses execution for review
- Add ApprovalRequest entity and ApprovalStatus enum
- Add approval API endpoints (list, approve, reject)
- Add Approval Inbox UI page with filtering and stats
- Add inline approve/reject buttons in execution timeline
- Fix WebSocket event serialization for StepId objects
- Add non-retryable error handling for APPROVAL_REJECTED
- Add execution resume after approval decision
Sprint 10: Gateways & Triggers implementation

Gateway Step (E7-01, E7-02, E7-03):
- Add gateway step type with exclusive/parallel/inclusive modes
- Implement ConditionEvaluator for boolean expressions (==, !=, >, <, and, or)
- GatewayHandler evaluates conditions and selects routes
- Gateway UI shows route taken and evaluated conditions
- Fix _build_step_outputs for proper condition evaluation

Webhook Triggers (E8-01, E8-02):
- Add TriggerConfig schema (WebhookTriggerConfig, ScheduleTriggerConfig stub)
- Implement /api/triggers endpoints (list, invoke, info)
- Triggers stored in ProcessDefinition and serialized correctly
- Optional secret authentication via X-Webhook-Secret header

UI Improvements:
- Execute input dialog for providing JSON input data
- ProcessList shows Eye icon for published (view) vs Pencil for draft (edit)
- ProcessEditor guards against editing published processes
- "New Version" button for creating drafts from published processes
- Add notification step type (slack, email stub, generic webhook)
- Add webhook event publisher for process_completed/failed/approval_requested
- Add compensation handlers for step rollback on process failure
- Add CompensationConfig value object for type-safe compensation
- Add compensation domain events (Started, Completed, Failed)
- Add trigger management UI with webhook URL copy functionality
- Add execute dialog for JSON input when starting processes

Reference: BACKLOG_MVP.md - E14-01, E14-02, E15-03, E13-03, E8-03
Bugs discovered during manual testing:

1. Notification YAML parsing: Added missing NOTIFICATION branch in
   StepDefinition.from_dict() - inline fields (channel, message, url)
   were not being extracted, causing all notifications to default to
   slack channel.

2. Compensation handler registry: Fixed get_handler() → get() method
   call on StepHandlerRegistry.

3. Compensation webhook URL: Added 'url' field mapping for generic
   webhook channel (was only setting webhook_url for Slack).

Test: compensation-test.yaml triggers intentional failure to verify
compensation handlers execute correctly.
UI/UX improvements for Sprint 11:

1. Retry Execution Tracking:
   - Add retry_of field to ProcessExecution aggregate
   - Link retry executions to original failed execution
   - Display amber "Retry of: <id>" badge with link to original

2. Real-time Compensation Events via WebSocket:
   - Add CompensationStarted/Completed/Failed to WebSocket publisher
   - Add compensation event handlers in useProcessWebSocket composable
   - Display compensation events in Event History without refresh

3. Event Display Improvements:
   - Format both snake_case and PascalCase event types
   - Clear events when navigating to different execution
   - Add 500ms delay before reload to ensure events are persisted
   - Style compensation events with appropriate colors
Sprint 12 Implementation (E9 - Timer & Scheduling):
- Add cron presets (hourly, daily, weekly, monthly, weekdays)
- Add cron expression validation to ProcessValidator
- Extend scheduler service with process schedule support
- Hook schedule registration into publish/archive lifecycle
- Add schedule trigger list/info API endpoints
- Create TimerHandler for timer step type
- Add schedule trigger UI in ProcessEditor
- Display next run time in ProcessList

Test Debt Fix (Sprints 9-11):
- Add test_approval_handler.py (24 tests) - S9
- Add test_gateway_handler.py (18 tests) - S10
- Add test_webhook_triggers.py (14 tests) - S10
- Add test_notification_handler.py (20 tests) - S11
- Add test_compensation.py (22 tests) - S11
- Add test_schedule_triggers.py (23 tests) - S12
- Add test_timer_handler.py (11 tests) - S12

Bug fixes:
- Fix timer.py: context.step -> context.step_definition
- Remove unused import in timer.py

Total: 466 tests passing (was 374)
Sprint 14 Implementation:
- E11-02: Process Analytics Dashboard with metrics, trends, step performance
- E11-03: Cost Alerts system with thresholds (per-execution, daily, weekly)
- E12-01: Process Template Library with bundled templates
- E12-02: Template creation from published processes

UI Improvements:
- Add Process sub-navigation (Processes, Dashboard, Executions, Approvals)
- Add Agent sub-navigation (Agents, Files, Templates)
- Declutter main nav from 8 to 6 items
- Template selector in process creation flow

Backend:
- Analytics service with ProcessMetrics, TrendData, StepPerformance
- CostAlertService with threshold management and alert generation
- ProcessTemplateService for bundled and user templates
- New API endpoints for analytics, alerts, and templates

Frontend:
- TrendChart component for execution/cost visualization
- TemplateSelector component for process templates
- ProcessSubNav and AgentSubNav components
- Alerts page and NavBar badge integration
- Enhanced ProcessDashboard with analytics

Tests:
- 59 new unit tests for analytics, alerts, and templates
Sprint 15 - Agent Roles (EMI Pattern):
- Add AgentRole enum (Executor/Monitor/Informed)
- Add StepRoles entity with validation
- Create InformedAgentNotifier service for async notifications
- Add RoleMatrix.vue component for interactive role management
- Integrate roles tab in ProcessEditor with UI-YAML sync
- Extend ProcessValidator for role validation across all step types

Sprint 12-14 Completion:
- E9-01: Cron validation already implemented (verified)
- E10-02: Add breadcrumb navigation for nested sub-process executions
- E11-01: Cost tracking already implemented (verified)
- E11-03: Integrate CostAlertService with ExecutionEngine
- E12-01: Add customer-support bundled process template

Tests:
- Add test_roles.py for EMI pattern validation
- Add test_informed_notifier.py for notification service
- Add ExecutionEngine cost alert integration tests

Process engine adds ~27,400 LOC (~41% growth to Trinity codebase)
- Move PROCESS_DRIVEN_PLATFORM from docs/drafts/ to docs/
- Add IT5 thinking iteration (scale, reliability, enterprise)
- Add Process Engine roadmap with 14 test processes across 5 phases
- Create feature flow documentation for Process Engine:
  - README overview, process-definition, process-execution
  - process-monitoring, human-approval, process-scheduling
  - process-analytics, sub-processes, agent-roles-emi, process-templates
- Update requirements.md: Add Section 18 (Process Engine) with all features
- Update roadmap.md: Mark Phase 14 complete, add decision log entry
- Update architecture.md: Add Process Engine section, API endpoints, DB schema
- Update feature-flows.md index with Process Engine section
vybe pushed a commit that referenced this pull request May 26, 2026
… column reads

Submodule moves to 9da562f. Skills (groom/roadmap/sprint/metrics/
read-docs) and DEVELOPMENT_WORKFLOW.md now derive Todo/In Progress/
In Dev/Done from status-* labels + GitHub open/closed state instead
of the Project #6 Status column, which drifted in practice. Project
board is still used for Rank/Tier/Epic/Theme.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vybe pushed a commit that referenced this pull request Jun 2, 2026
…Settings (#995) (#996)

* feat(enterprise-ui): User & Org Management view on the #847 seam (#995)

Public OSS-bundle frontend for the private user_management module
(Abilityai/trinity-enterprise#2). Gated entirely server-side by the
`user_management` entitlement — hidden in OSS-only builds and bounced by
the route guard on direct URL visits.

- views/enterprise/UserManagement.vue: org list + create, membership
  add/remove, seat counts. Light + dark. No algorithmic IP (CRUD glue
  over the private /api/enterprise/user-management/* endpoints).
- stores/orgManagement.js: domain store, calls via shared axios + auth
  header (Invariants #6/#7).
- router: /enterprise/user-management gated meta.requiresEntitlement:
  'user_management' (mirrors the audit route).
- views/enterprise/Index.vue: add the catalogue card (available).

No public backend/schema/model changes — the entire data model + logic
lives in the private submodule per the enterprise open-core split. The
submodule pointer is intentionally NOT bumped here; it advances after
trinity-enterprise#2 merges.

Verified: all four files compile; the only build blocker is the
pre-existing unrelated `mermaid` import in AgentWorkspace.vue (stale
local node_modules; resolved by CI npm ci).

Related to #995, #847

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(enterprise-ui): per-user activity audit in Settings → User Management (#995)

Integrates the enterprise activity view INTO the existing OSS user
management table (not a separate page). When user_management is
entitled, each user row gets a "View activity" action opening a drawer
with that user's audit summary + timeline, fetched from the private
/api/enterprise/user-management/users/{id}/activity endpoint.

- Gated entirely by enterpriseStore.isEntitled('user_management') —
  column + drawer hidden in OSS-only builds.
- No change to the existing role-CRUD behaviour; purely additive column.
- loadFeatureFlags() in onMounted (cached/no-op when NavBar already ran).

Related to #995
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(user-mgmt): OSS deactivation primitive + enterprise lifecycle UI (#995)

Pivots #995 from Organizations to the real net-new gap — user
onboarding/offboarding — integrated into the existing Settings → User
Management table (not a separate page).

OSS primitive (edition-agnostic, small):
- users.suspended_at column + migration + surfaced in get_user/list_users.
- get_current_user rejects suspended users (both JWT + MCP-key paths), so
  setting the column blocks new logins AND invalidates live tokens on the
  next request.
- /api/users exposes suspended_at (read-only) so the gated UI can render
  Deactivate/Reactivate.

Enterprise UI (gated by user_management entitlement, hidden in OSS):
- Settings → User Management gains an "Invite user" form, per-row
  Deactivate/Reactivate (not for self or the built-in admin), and the
  per-user Activity drawer. All call the private
  /api/enterprise/user-management/* endpoints.

Removed: the separate /enterprise/user-management Orgs page, its route,
store, and Index card (orgs dropped — single-tenant). Index card now
points at Settings.

No change to the existing OSS role-CRUD behaviour. Verified live:
/api/users carries suspended_at; suspend/reactivate/invite/activity all
work; OSS-only builds hide every enterprise control.

Related to #995, #847
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(user-mgmt): stop Management column clipping in User Management table (#995)

The users table wrapper was overflow-hidden; the extra entitlement-gated
Management column pushed total width past the card and clipped the
right-side action buttons. Switch to overflow-x-auto so the wider table
scrolls within the card instead of clipping.

Related to #995
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(user-mgmt): fit User Management table in the card (no h-scroll) (#995)

Replace the overflow-x-auto stopgap with an actual fit: trim cell padding
px-6→px-4 across the table and let the Management actions wrap within
their column (flex-wrap, text-xs, no whitespace-nowrap). The 5-column
table now fits the max-w-4xl settings card without clipping or a
horizontal scrollbar.

Related to #995
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add suspended_at to schedule-soft-delete test users DDL (#995)

The #995 users.suspended_at primitive added the column to _USER_COLUMNS,
so get_user_by_*() now SELECTs it. test_schedule_soft_delete builds its
own users table with a hardcoded DDL that lacked the column, causing
"no such column: suspended_at" (4 regression-diff failures). Mirror the
schema change in the test DDL.

Related to #995
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(architecture): document enterprise modules + two-track migrations (#995/#997)

- users.suspended_at deactivation primitive (OSS column + enforcement;
  enterprise-only setter) on the users table + a callout.
- Invariant #3 extended: enterprise migrates enterprise_* tables via a
  separate runner tracked in enterprise_schema_migrations (one file per
  migration; never ALTERs OSS tables; runs after OSS init).
- feature-flags doc gains enterprise_features.
- New "Enterprise Modules (#847 seam)" section: audit / user_management /
  siem entitlements, surfaces, and the gating model.

Related to #995, #997, #847
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(enterprise): enterprise registration failure must not crash core boot (#995/#997)

main.py wrapped register_enterprise(app) in `except ImportError` only — so a
bug in enterprise registration (schema init, migration, router mount, pusher
start) would propagate and crash backend startup on an enterprise build.

Add a broad `except Exception` that logs loudly + a traceback and continues
in OSS-only mode. Modules registered before the failure stay active; the
rest are simply absent from feature-flags. The core platform always boots.

OSS-only builds are unaffected (still the ImportError path). Verified: happy
path still boots (health 200) and registers ['audit','siem'].

Related to #995, #997, #847
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vybe added a commit that referenced this pull request Jun 3, 2026
#1043)

* docs: deprecate Project #6 board — issues-only SDLC (#1042)

Move all prioritization metadata off GitHub Project #6 to labels +
native sub-issues. Companion to the .claude submodule rewrite
(trinity-dev#2), pointer bumped here.

- CLAUDE.md: Current Product Focus recast around theme-* labels;
  Rules of Engagement + SDLC lifecycle de-boarded (Todo → In Progress
  → In Dev → Done); ordering = priority, bug-before-feature, newest first.
- docs/GITHUB_ISSUES_MIGRATION.md: appended 2026-06-03 archive note
  (board fields → label/sub-issue replacements, dropped Tier/Rank,
  removed legacy P1b + status-review labels).
- docs/planning/PROJECT_BOARD_DEPRECATION_2026-05.md: execution addendum
  recording the two operator overrides (newest-first ordering; complexity
  kept as labels).

GitHub-state migration applied out-of-band: theme-*/complexity-* labels
backfilled from board data, epics converted to type-epic + sub-issues,
Project #6 archived.

Fixes #1042

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump .claude to squashed main after trinity-dev#2 (#1042)

Re-point the submodule from the feature-branch commit to the squashed
main commit (trinity-dev#2 merged). Coordinated with #1042.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Eugene Vyborov <eugene@beingluminous.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vybe pushed a commit that referenced this pull request Jun 26, 2026
…ffects (#1084) (#1351)

* feat(idempotency): effect-scoped guard primitive for outbound side effects (#1084)

Trigger-boundary idempotency (#525, Invariant #18) dedups the execution but
not an agent's individual tool calls — so a re-delivered turn (the at-least-once
semantics pull-mode / work-stealing will introduce, Epic #1045/#1081) re-emits
the same outbound effect. Add the per-sink primitive that enforces exactly-once
external effects at the sink, keyed on resolved effect identity.

In services/idempotency_service.py (no new module — avoids the untracked-file /
Docker-COPY crash class):
- make_effect_scope(execution_id) → effect:{execution_id}
- make_payment_scope(agent_request_id) → payment:{agent_request_id}
- derive_effect_key: {effect_type}:sha256(execution_id \x00 effect_type \x00
  resolved_identifying_args \x00 dedup_label) — STABLE identity only, never the
  LLM-generated body (non-deterministic across a re-run → would defeat dedup);
  dedup_label lets an agent intentionally send two distinct messages per turn.
- resolve_and_validate_execution: generalizes the MEM-001 server-side resolution
  (the agent supplies execution_id, never its own identity); FAIL-OPEN on
  missing/invalid/mismatch so an old image or absent arg never 5xxes a real send.
- effect_guard async context manager: completed replay → snapshot (no re-send);
  in_flight replay → raises retryable EffectInProgressError (codex #6, never a
  silent None-as-success); fresh claim → complete() on clean exit, fail() (release
  for retry) on exception. Payment scope path for Nevermined (native token).

Reuses the existing 24h-default claim TTL (already exceeds the lease window), so
a completed row outlives a late re-delivery — no new TTL plumbing.

TDD: 22 new unit tests in tests/test_idempotency.py (40 total green) — body
independence, dedup_label, fresh-execution-id, in_flight-raises, fail-open,
6h-aged replay, payment scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(payments): guard Nevermined settle with payment-scoped effect idempotency (#1084)

The highest-stakes sink first. Add NeverminedPaymentService.settle_payment_once,
which wraps settle_payment in the effect guard keyed on payment:{agent_request_id}
— Nevermined's native exactly-once token IS the dedup scope. A retried paid chat
reusing the same token replays the stored settle receipt instead of burning
credits twice; the native token remains the real on-chain guarantee, this avoids
even attempting a duplicate and gives a fast local replay.

- completed replay → returns the stored sanitized receipt (never the raw SDK
  object), no re-settle.
- a FAILED settle raises an internal _SettleNotCompleted so the in_flight claim
  is RELEASED (only a successful settle persists as completed/replayable) → a
  later retry can re-attempt.
- a concurrent in-flight settle → retryable "already in progress" result, never
  a silent skip (codex #6).
- no agent_request_id → fail-open, settle proceeds without dedup.

routers/paid.py Step 4 now calls settle_payment_once; the existing terminal-turn
guard (failed execution → no settle, prior learning terminal-applier-status-leak)
is the preserved outer layer.

TDD: 4 wiring tests (retried→one settle, failed→release+retry, distinct tokens
→both, missing token→fail-open). 44 idempotency tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(messages): effect-guard proactive send_message on resolved recipient+channel (#1084)

Thread execution_id + dedup_label through routers/messages.py → send_message,
and wrap the entry in the effect guard keyed on the RESOLVED recipient + channel
(never the LLM-generated body, which is non-deterministic across a re-run). When
the turn it ran in is re-delivered (pull-mode at-least-once), the same identity
within one execution dedupes to exactly one send; a distinct dedup_label lets an
agent intentionally send two messages to the same recipient per turn.

- send_message refactored: the entry guard wraps a new _send_message_inner (auth
  → rate-limit → channel delivery), so a success records a replay snapshot and an
  exception releases the claim for retry. Replay reconstructs the DeliveryResult.
- A chunked message is ONE effect (entry guard) — a mid-chunk crash re-sends the
  whole message on retry (at-least-once for chunks, documented).
- Concurrent in-flight duplicate → EffectInProgressError → router returns 409
  (retryable), never a silent skip-and-succeed (codex #6).
- Fail-open when execution_id absent/invalid (old image).

Drive-by: removed two dead imports (time, enum.Enum) + one stray f-prefix to keep
the touched files ruff-clean.

TDD: 4 wiring tests (re-delivery→one send, dedup_label→two, no-execution_id→
fail-open, distinct-recipient→not-deduped). 48 idempotency tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(voip): effect-guard call_user on resolved dial target (#1084)

Thread execution_id + dedup_label through routers/voip.py → place_outbound_call,
and wrap the dial in the effect guard keyed on the RESOLVED dial target + Twilio
account, scoped to execution_id. When the turn it ran in is re-delivered
(pull-mode at-least-once), the same number within one execution replays the
original {call_id, status, ...} instead of placing a second PSTN call.

- place_outbound_call refactored: the entry guard wraps a new _place_call_inner
  (abuse controls → stage Gemini intent → dial Twilio), so a success records a
  replay snapshot and a raised HTTPException releases the claim for retry.
- The router's boundary Idempotency-Key gate stays the OUTER layer (kept).
- Concurrent in-flight duplicate dial → EffectInProgressError → router releases
  the outer trigger claim and returns 409 (retryable), never a silent skip.
- Fail-open when execution_id absent/invalid (old image).

Drive-by: removed one dead twilio import to keep the file ruff-clean.

TDD: 4 wiring tests (re-delivery→one dial, dedup_label→two, no-execution_id→
fail-open, distinct-number→not-deduped). 52 idempotency tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): effect-guard share_file on filename + content version (#1084)

Thread execution_id + dedup_label through routers/agent_files.py → create_share,
and wrap the token-mint + persist in the effect guard keyed on the filename + a
sha256 of the extracted CONTENT, scoped to execution_id. A re-run of the same
turn sharing the same file replays the original signed URL instead of minting a
second token; a changed file under the same name produces a new share.

- create_share extracts up-front (so the key can version on content), then the
  guard wraps a new _finalize_share (MIME/quota/disk → persist → mint token + DB
  row → URL). Success records the URL snapshot for replay; an exception releases
  the claim for retry.
- ShareFileMcpRequest gains execution_id + dedup_label; the internal agent-server
  share path passes neither → fail-open (back-compat).
- Concurrent in-flight duplicate → EffectInProgressError → router 409.

Drive-by: removed a dead typing.Optional import to keep agent_files.py ruff-clean.

TDD: 3 wiring tests (re-run→same URL replay, changed-content→new share,
no-execution_id→fail-open). 55 idempotency tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mcp): execution_id + dedup_label params on send_message / call_user / share_file (#1084)

Inv #13 third surface. Add optional agent-supplied execution_id + dedup_label
params to the messages.ts (send_message), voip.ts (call_user) and files.ts
(share_file) tools, and thread them into the request bodies in client.ts
(sendUserMessage / placeVoipCall / shareAgentFile).

The agent reads execution_id from the 'Execution Context' block of its system
prompt (same source as write_user_memory / MEM-001) and passes it so the backend
effect guard dedupes a re-delivered turn to exactly one effect; omitting it is
fail-open (the send proceeds). dedup_label lets the agent intentionally repeat an
effect to the same target in one turn. Trusted runtime injection of execution_id
(+ fail-closed-when-absent) is deferred — a BLOCKING prerequisite on Epic
#1045/#1081 before pull-mode default-ON for side-effect agents.

Tests: new messages.test.ts asserts execution_id + dedup_label reach the body
(and undefined when omitted), plus a regression assertion that chat_with_agent
still derives a non-empty mcp: Idempotency-Key. 31 MCP tests green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(reliability): effect-idempotency contract + architecture + pull-mode gate (#1084)

- New docs/memory/feature-flows/effect-idempotency.md: the per-sink exactly-once
  contract — honest scope (local suppression vs true exactly-once), the 6 design
  decisions, wired sinks table, the BLOCKING pull-mode-default-ON prerequisite,
  and the documented failure modes (pre-call crash window, chunk at-least-once,
  git idempotent-by-construction).
- architecture.md: extend the RELIABILITY-006 (#525) Idempotency block with the
  #1084 effect-scoped layer (effect_guard, scopes, body-independent key,
  in_flight≠completed, 24h-TTL reuse, wired sinks, the gate).
- feature-flows.md: Recent Updates row + a Documented Flows entry.
- task_execution_service.py: dispatch_async_eligible carries the pull-mode gate
  comment (trusted execution_id injection + fail-closed-when-absent required
  before pull-mode default-ON for side-effect agents — Epic #1045/#1081).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(security): CSO --diff audit for effect-scoped idempotency (#1084)

CLEAR — no CRITICAL/HIGH. Confirms the load-bearing auth property: a forged or
borrowed execution_id fails-open (no claim, no cross-tenant snapshot read) because
agent_name is always authenticated and resolve_and_validate_execution checks
ownership before the claim. No secrets, no injection (agent-supplied
identifying_args/dedup_label are hash-only), no new deps, no new SQL. One LOW noted
(share download token at rest in idempotency_keys — same backend-internal trust
level, no net exposure).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(idempotency): harden effect-guard replay + cap key inputs (#1084)

- Bound execution_id/dedup_label with max_length=200 on send_message and
  call_user request models so a hostile/oversized value can't bloat the
  derived effect key.
- Replay-with-missing-snapshot (I2): a completed effect replay is terminal,
  so never re-emit the side effect even when the stored snapshot is
  NULL/unparseable. share_file raises 409 (cannot rebuild the signed URL);
  Nevermined settle reports success without a receipt and logs a warning
  (never re-enters the external settle path → no double-charge).
- Track the _finalize_share → _persist_and_register rename in the
  share_file guard test.

Refs #1084

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(test): #679 paid-settle test mocks settle_payment_once after #1084 reroute

paid_chat's success path now settles through settle_payment_once (the
effect-scoped idempotency wrapper, #1084) rather than bare settle_payment,
so the #679 success-still-settles regression test must mock/await the new
entrypoint. The cancel/fail siblings keep asserting non-settle (neither
entrypoint is reached on a terminal turn).

Refs #1084

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(reliability): requirements §10.10.1 + sink-flow pointers for effect idempotency (#1084)

Adds requirements.md §10.10.1 documenting the effect-scoped extension to
RELIABILITY-006, and cross-reference pointers from the four wired sink flows
(voip-telephony, proactive-messaging, file-sharing-outbound, nevermined-payments)
to the canonical effect-idempotency flow (one-home-per-feature rule).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vybe pushed a commit that referenced this pull request Jul 29, 2026
…ab (ent#235) (#1877)

* feat(ui): Skills management surface — unhide and rebuild the Skills tab (ent#235)

The skills machinery shipped across three planes (#182 distribute/place/expose,
#183 package injection with a per-skill result contract) and nothing rendered
any of it. The Agent Detail Skills tab was excluded from `visibleTabs` per
requirements §22.2 ("component preserved for potential admin-only access"),
assignment was REST/MCP-only (§21.3), and #183's statuses and named warnings had
no consumer at all. A user could not browse the library, see what an agent had,
or assign anything.

What lands:

* **Tab unhidden** for owners/admins on non-system agents, matching the other
  management tabs. `OverflowTabs` absorbs it.

* **`stores/skills.js`** — a domain store (Invariant #6). The old panel called
  `axios` directly with a hand-built auth header, silently bypassing the shared
  client every other call relies on; everything now goes through `api`
  (Invariant #7).

* **Library browse** with the §21.6 contract surfaced: description, automation,
  `user_invocable`, declared `requires` (binaries/packages/env), multi-file file
  count, size, and the git tree SHA as version. Dependencies are shown BEFORE
  assignment, because they are what later becomes a `missing_binary:*` warning.

* **Assignment** with bulk save through the existing `PUT .../skills`, plus a
  dirty/reset affordance so a half-made selection is recoverable.

* **Honest injection status.** This is the load-bearing part. #183 reports
  `injected | unchanged | fallback | failed` with named warnings; the panel
  renders the verdict per skill and translates the tokens into what they mean
  for THIS agent ("`jq` is not installed in this agent — the skill may not
  run"). `fallback` renders as "partial" in amber, never a green tick — an
  explicit AC. Injection results are kept separate from assignment in the store
  precisely so a durable assignment cannot be painted with a stale success.

* **Manual sync** (`force=True` repair action) with in-flight state, and a 409
  from `SkillInjectionBusy` reported as "already running" rather than a generic
  failure.

* **No dead empty states** — the store computes one discriminator
  (`library_unconfigured` / `library_empty` / `none_assigned`) so the panel
  cannot invent a fourth. Unconfigured routes an admin to Settings and tells a
  non-admin to ask one.

* **Stopped agent** renders persisted assignment state with Sync disabled and
  the reason in the tooltip, rather than offering an action that would fail.

Verified against the live instance: tab appears, 3-skill library renders with
contract fields, bulk assign persists, agent started, "Sync now" returns
`{haiku: injected/2 files, word-count: injected/2 files}` and the badges +
last-sync line render from that response.

Gating confirmed OSS-core with the issue author before building: every file here
is already public, the endpoints are ungated, and the paid piece (skill_runner,
ent#139) plus exposure curation (#178) are both explicitly out of scope.

Related to trinity-enterprise#235

* fix(ui): dead Settings link + error swallowed as an empty library (ent#235 review)

Self-review of #1877 found two defects, both in the "no dead empty states" AC
this panel exists to satisfy.

1) The "Configure the library" CTA linked to `/settings?tab=skills`. There is no
   such tab — the Skills Library config lives under Settings → **agents**
   (`Settings.vue`, `v-if="activeTab === 'agents'"`). So the one call-to-action
   offered to an admin staring at an unconfigured library went nowhere. I also
   asserted in the PR body that the Settings panel already reported sync status
   / last-synced / skill count without checking; it does report all three — but
   I had the tab wrong, which is what checking would have caught.

2) `api.get('/api/skills/library').catch(() => ({ data: [] }))` swallowed every
   error, not just the unconfigured case: a 500, a timeout or an auth failure
   all rendered as "the library is configured but has no skills yet" — a
   confident, wrong empty state that points the operator at the wrong problem.
   The list is now fetched only when `status.configured` is true, so the known
   empty state comes from the status read and any other failure surfaces as one.

Related to trinity-enterprise#235
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants