gobby 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Workflow audit log storage manager.
|
|
2
|
+
|
|
3
|
+
Provides persistent storage for workflow decisions (tool permissions,
|
|
4
|
+
rule evaluations, step transitions) for explainability and debugging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from gobby.storage.database import DatabaseProtocol, LocalDatabase
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class WorkflowAuditEntry:
|
|
20
|
+
"""A single workflow audit log entry."""
|
|
21
|
+
|
|
22
|
+
session_id: str
|
|
23
|
+
step: str
|
|
24
|
+
event_type: str # 'tool_call', 'rule_eval', 'transition', 'exit_check', 'approval'
|
|
25
|
+
result: str # 'allow', 'block', 'transition', 'skip', 'approved', 'rejected', 'pending'
|
|
26
|
+
reason: str | None = None
|
|
27
|
+
tool_name: str | None = None
|
|
28
|
+
rule_id: str | None = None
|
|
29
|
+
condition: str | None = None
|
|
30
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
32
|
+
id: int | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorkflowAuditManager:
|
|
36
|
+
"""Manages workflow audit log entries in SQLite."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, db: DatabaseProtocol | None = None):
|
|
39
|
+
"""Initialize the audit manager.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
db: Optional database instance. If None, creates a new one.
|
|
43
|
+
"""
|
|
44
|
+
self._db = db or LocalDatabase()
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def db(self) -> DatabaseProtocol:
|
|
48
|
+
"""Get database instance."""
|
|
49
|
+
return self._db
|
|
50
|
+
|
|
51
|
+
def log(
|
|
52
|
+
self,
|
|
53
|
+
session_id: str,
|
|
54
|
+
step: str,
|
|
55
|
+
event_type: str,
|
|
56
|
+
result: str,
|
|
57
|
+
reason: str | None = None,
|
|
58
|
+
tool_name: str | None = None,
|
|
59
|
+
rule_id: str | None = None,
|
|
60
|
+
condition: str | None = None,
|
|
61
|
+
context: dict[str, Any] | None = None,
|
|
62
|
+
) -> int | None:
|
|
63
|
+
"""Log a workflow audit entry.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
session_id: The session this entry belongs to
|
|
67
|
+
step: Current workflow step
|
|
68
|
+
event_type: Type of event ('tool_call', 'rule_eval', etc.)
|
|
69
|
+
result: Result of the evaluation ('allow', 'block', etc.)
|
|
70
|
+
reason: Human-readable explanation
|
|
71
|
+
tool_name: Name of tool (for tool_call events)
|
|
72
|
+
rule_id: Rule identifier (for rule_eval events)
|
|
73
|
+
condition: The 'when' clause evaluated
|
|
74
|
+
context: Additional JSON context
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The inserted row ID, or None on failure.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
81
|
+
context_json = json.dumps(context) if context else None
|
|
82
|
+
|
|
83
|
+
cursor = self.db.execute(
|
|
84
|
+
"""
|
|
85
|
+
INSERT INTO workflow_audit_log
|
|
86
|
+
(session_id, timestamp, step, event_type, tool_name, rule_id, condition, result, reason, context)
|
|
87
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
88
|
+
""",
|
|
89
|
+
(
|
|
90
|
+
session_id,
|
|
91
|
+
timestamp,
|
|
92
|
+
step,
|
|
93
|
+
event_type,
|
|
94
|
+
tool_name,
|
|
95
|
+
rule_id,
|
|
96
|
+
condition,
|
|
97
|
+
result,
|
|
98
|
+
reason,
|
|
99
|
+
context_json,
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
return cursor.lastrowid
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Failed to log audit entry: {e}")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
def log_tool_call(
|
|
108
|
+
self,
|
|
109
|
+
session_id: str,
|
|
110
|
+
step: str,
|
|
111
|
+
tool_name: str,
|
|
112
|
+
result: str,
|
|
113
|
+
reason: str | None = None,
|
|
114
|
+
context: dict[str, Any] | None = None,
|
|
115
|
+
) -> int | None:
|
|
116
|
+
"""Log a tool call permission check.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
session_id: Session ID
|
|
120
|
+
step: Current step
|
|
121
|
+
tool_name: Name of the tool
|
|
122
|
+
result: 'allow' or 'block'
|
|
123
|
+
reason: Why the tool was allowed/blocked
|
|
124
|
+
context: Additional context (tool args, etc.)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Row ID or None.
|
|
128
|
+
"""
|
|
129
|
+
return self.log(
|
|
130
|
+
session_id=session_id,
|
|
131
|
+
step=step,
|
|
132
|
+
event_type="tool_call",
|
|
133
|
+
result=result,
|
|
134
|
+
reason=reason,
|
|
135
|
+
tool_name=tool_name,
|
|
136
|
+
context=context,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def log_rule_eval(
|
|
140
|
+
self,
|
|
141
|
+
session_id: str,
|
|
142
|
+
step: str,
|
|
143
|
+
rule_id: str,
|
|
144
|
+
condition: str,
|
|
145
|
+
result: str,
|
|
146
|
+
reason: str | None = None,
|
|
147
|
+
context: dict[str, Any] | None = None,
|
|
148
|
+
) -> int | None:
|
|
149
|
+
"""Log a rule evaluation.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
session_id: Session ID
|
|
153
|
+
step: Current step
|
|
154
|
+
rule_id: Identifier for the rule
|
|
155
|
+
condition: The 'when' clause
|
|
156
|
+
result: 'allow', 'block', 'skip'
|
|
157
|
+
reason: Why the rule fired/didn't fire
|
|
158
|
+
context: Additional context
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Row ID or None.
|
|
162
|
+
"""
|
|
163
|
+
return self.log(
|
|
164
|
+
session_id=session_id,
|
|
165
|
+
step=step,
|
|
166
|
+
event_type="rule_eval",
|
|
167
|
+
result=result,
|
|
168
|
+
reason=reason,
|
|
169
|
+
rule_id=rule_id,
|
|
170
|
+
condition=condition,
|
|
171
|
+
context=context,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def log_transition(
|
|
175
|
+
self,
|
|
176
|
+
session_id: str,
|
|
177
|
+
from_step: str,
|
|
178
|
+
to_step: str,
|
|
179
|
+
reason: str | None = None,
|
|
180
|
+
context: dict[str, Any] | None = None,
|
|
181
|
+
) -> int | None:
|
|
182
|
+
"""Log a step transition.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
session_id: Session ID
|
|
186
|
+
from_step: Step transitioning from
|
|
187
|
+
to_step: Step transitioning to
|
|
188
|
+
reason: Why the transition occurred
|
|
189
|
+
context: Additional context (trigger condition, etc.)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Row ID or None.
|
|
193
|
+
"""
|
|
194
|
+
ctx = context or {}
|
|
195
|
+
ctx["from_step"] = from_step
|
|
196
|
+
ctx["to_step"] = to_step
|
|
197
|
+
return self.log(
|
|
198
|
+
session_id=session_id,
|
|
199
|
+
step=from_step,
|
|
200
|
+
event_type="transition",
|
|
201
|
+
result="transition",
|
|
202
|
+
reason=reason or f"Transitioned to '{to_step}'",
|
|
203
|
+
context=ctx,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def log_exit_check(
|
|
207
|
+
self,
|
|
208
|
+
session_id: str,
|
|
209
|
+
step: str,
|
|
210
|
+
condition: str,
|
|
211
|
+
result: str,
|
|
212
|
+
reason: str | None = None,
|
|
213
|
+
context: dict[str, Any] | None = None,
|
|
214
|
+
) -> int | None:
|
|
215
|
+
"""Log an exit condition check.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
session_id: Session ID
|
|
219
|
+
step: Current step
|
|
220
|
+
condition: The exit condition being checked
|
|
221
|
+
result: 'met' or 'unmet'
|
|
222
|
+
reason: Why the condition was met/unmet
|
|
223
|
+
context: Additional context
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Row ID or None.
|
|
227
|
+
"""
|
|
228
|
+
return self.log(
|
|
229
|
+
session_id=session_id,
|
|
230
|
+
step=step,
|
|
231
|
+
event_type="exit_check",
|
|
232
|
+
result=result,
|
|
233
|
+
reason=reason,
|
|
234
|
+
condition=condition,
|
|
235
|
+
context=context,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def log_approval(
|
|
239
|
+
self,
|
|
240
|
+
session_id: str,
|
|
241
|
+
step: str,
|
|
242
|
+
result: str,
|
|
243
|
+
condition_id: str | None = None,
|
|
244
|
+
prompt: str | None = None,
|
|
245
|
+
context: dict[str, Any] | None = None,
|
|
246
|
+
) -> int | None:
|
|
247
|
+
"""Log an approval gate event.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
session_id: Session ID
|
|
251
|
+
step: Current step
|
|
252
|
+
result: 'approved', 'rejected', 'pending', 'timeout'
|
|
253
|
+
condition_id: The approval condition ID
|
|
254
|
+
prompt: The approval prompt shown
|
|
255
|
+
context: Additional context
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Row ID or None.
|
|
259
|
+
"""
|
|
260
|
+
ctx = context or {}
|
|
261
|
+
if condition_id:
|
|
262
|
+
ctx["condition_id"] = condition_id
|
|
263
|
+
if prompt:
|
|
264
|
+
ctx["prompt"] = prompt
|
|
265
|
+
return self.log(
|
|
266
|
+
session_id=session_id,
|
|
267
|
+
step=step,
|
|
268
|
+
event_type="approval",
|
|
269
|
+
result=result,
|
|
270
|
+
reason=prompt,
|
|
271
|
+
context=ctx,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def get_entries(
|
|
275
|
+
self,
|
|
276
|
+
session_id: str | None = None,
|
|
277
|
+
event_type: str | None = None,
|
|
278
|
+
result: str | None = None,
|
|
279
|
+
limit: int = 50,
|
|
280
|
+
offset: int = 0,
|
|
281
|
+
) -> list[WorkflowAuditEntry]:
|
|
282
|
+
"""Get audit log entries with optional filters.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
session_id: Filter by session ID
|
|
286
|
+
event_type: Filter by event type
|
|
287
|
+
result: Filter by result
|
|
288
|
+
limit: Maximum entries to return
|
|
289
|
+
offset: Number of entries to skip
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of WorkflowAuditEntry objects.
|
|
293
|
+
"""
|
|
294
|
+
conditions = []
|
|
295
|
+
params: list[Any] = []
|
|
296
|
+
|
|
297
|
+
if session_id:
|
|
298
|
+
conditions.append("session_id = ?")
|
|
299
|
+
params.append(session_id)
|
|
300
|
+
if event_type:
|
|
301
|
+
conditions.append("event_type = ?")
|
|
302
|
+
params.append(event_type)
|
|
303
|
+
if result:
|
|
304
|
+
conditions.append("result = ?")
|
|
305
|
+
params.append(result)
|
|
306
|
+
|
|
307
|
+
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
|
308
|
+
params.extend([limit, offset])
|
|
309
|
+
|
|
310
|
+
# nosec B608: where_clause built from hardcoded condition strings, values parameterized
|
|
311
|
+
rows = self.db.fetchall(
|
|
312
|
+
f"""
|
|
313
|
+
SELECT id, session_id, timestamp, step, event_type, tool_name,
|
|
314
|
+
rule_id, condition, result, reason, context
|
|
315
|
+
FROM workflow_audit_log
|
|
316
|
+
WHERE {where_clause}
|
|
317
|
+
ORDER BY timestamp DESC
|
|
318
|
+
LIMIT ? OFFSET ?
|
|
319
|
+
""", # nosec B608
|
|
320
|
+
tuple(params),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
entries = []
|
|
324
|
+
for row in rows:
|
|
325
|
+
context_data = {}
|
|
326
|
+
if row["context"]:
|
|
327
|
+
try:
|
|
328
|
+
context_data = json.loads(row["context"])
|
|
329
|
+
except json.JSONDecodeError:
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
timestamp = (
|
|
333
|
+
datetime.fromisoformat(row["timestamp"]) if row["timestamp"] else datetime.now(UTC)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
entries.append(
|
|
337
|
+
WorkflowAuditEntry(
|
|
338
|
+
id=row["id"],
|
|
339
|
+
session_id=row["session_id"],
|
|
340
|
+
timestamp=timestamp,
|
|
341
|
+
step=row["step"],
|
|
342
|
+
event_type=row["event_type"],
|
|
343
|
+
tool_name=row["tool_name"],
|
|
344
|
+
rule_id=row["rule_id"],
|
|
345
|
+
condition=row["condition"],
|
|
346
|
+
result=row["result"],
|
|
347
|
+
reason=row["reason"],
|
|
348
|
+
context=context_data,
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return entries
|
|
353
|
+
|
|
354
|
+
def cleanup_old_entries(self, days: int = 7) -> int:
|
|
355
|
+
"""Delete audit entries older than the specified number of days.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
days: Number of days to retain entries (default: 7)
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Number of entries deleted.
|
|
362
|
+
"""
|
|
363
|
+
try:
|
|
364
|
+
cursor = self.db.execute(
|
|
365
|
+
"""
|
|
366
|
+
DELETE FROM workflow_audit_log
|
|
367
|
+
WHERE datetime(timestamp) < datetime('now', ? || ' days')
|
|
368
|
+
""",
|
|
369
|
+
(f"-{days}",),
|
|
370
|
+
)
|
|
371
|
+
return cursor.rowcount
|
|
372
|
+
except Exception as e:
|
|
373
|
+
logger.error(f"Failed to cleanup audit entries: {e}")
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
def get_entry_count(self, session_id: str | None = None) -> int:
|
|
377
|
+
"""Get the total number of audit entries.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
session_id: Optional session ID filter
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Total count of entries.
|
|
384
|
+
"""
|
|
385
|
+
if session_id:
|
|
386
|
+
row = self.db.fetchone(
|
|
387
|
+
"SELECT COUNT(*) as count FROM workflow_audit_log WHERE session_id = ?",
|
|
388
|
+
(session_id,),
|
|
389
|
+
)
|
|
390
|
+
else:
|
|
391
|
+
row = self.db.fetchone("SELECT COUNT(*) as count FROM workflow_audit_log")
|
|
392
|
+
|
|
393
|
+
return row["count"] if row else 0
|