tradingcodex 0.1.0a1__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.
- apps/__init__.py +1 -0
- apps/audit/__init__.py +1 -0
- apps/audit/admin.py +11 -0
- apps/audit/apps.py +8 -0
- apps/audit/migrations/0001_initial.py +35 -0
- apps/audit/migrations/__init__.py +0 -0
- apps/audit/models.py +22 -0
- apps/harness/__init__.py +1 -0
- apps/harness/admin.py +52 -0
- apps/harness/apps.py +8 -0
- apps/harness/migrations/0001_initial.py +68 -0
- apps/harness/migrations/__init__.py +0 -0
- apps/harness/models.py +55 -0
- apps/harness/services.py +49 -0
- apps/harness/templatetags/__init__.py +1 -0
- apps/harness/templatetags/tradingcodex_admin.py +111 -0
- apps/integrations/__init__.py +1 -0
- apps/integrations/admin.py +24 -0
- apps/integrations/apps.py +8 -0
- apps/integrations/migrations/0001_initial.py +29 -0
- apps/integrations/migrations/__init__.py +0 -0
- apps/integrations/models.py +16 -0
- apps/integrations/services.py +31 -0
- apps/mcp/__init__.py +1 -0
- apps/mcp/admin.py +33 -0
- apps/mcp/apps.py +8 -0
- apps/mcp/migrations/0001_initial.py +57 -0
- apps/mcp/migrations/0002_mcptooldefinition_experimental.py +18 -0
- apps/mcp/migrations/__init__.py +0 -0
- apps/mcp/models.py +45 -0
- apps/mcp/services.py +26 -0
- apps/orders/__init__.py +1 -0
- apps/orders/admin.py +27 -0
- apps/orders/apps.py +8 -0
- apps/orders/migrations/0001_initial.py +79 -0
- apps/orders/migrations/__init__.py +0 -0
- apps/orders/models.py +66 -0
- apps/orders/services.py +84 -0
- apps/policy/__init__.py +1 -0
- apps/policy/admin.py +60 -0
- apps/policy/apps.py +8 -0
- apps/policy/migrations/0001_initial.py +75 -0
- apps/policy/migrations/__init__.py +0 -0
- apps/policy/models.py +61 -0
- apps/policy/services.py +110 -0
- apps/portfolio/__init__.py +1 -0
- apps/portfolio/admin.py +21 -0
- apps/portfolio/apps.py +8 -0
- apps/portfolio/migrations/0001_initial.py +67 -0
- apps/portfolio/migrations/__init__.py +0 -0
- apps/portfolio/models.py +53 -0
- apps/research/__init__.py +1 -0
- apps/research/admin.py +42 -0
- apps/research/apps.py +8 -0
- apps/research/migrations/0001_initial.py +99 -0
- apps/research/migrations/__init__.py +0 -0
- apps/research/models.py +85 -0
- apps/universes/__init__.py +1 -0
- apps/universes/admin.py +10 -0
- apps/universes/apps.py +8 -0
- apps/universes/migrations/0001_initial.py +29 -0
- apps/universes/migrations/__init__.py +0 -0
- apps/universes/models.py +16 -0
- apps/workflows/__init__.py +1 -0
- apps/workflows/admin.py +17 -0
- apps/workflows/apps.py +8 -0
- apps/workflows/migrations/0001_initial.py +50 -0
- apps/workflows/migrations/__init__.py +0 -0
- apps/workflows/models.py +36 -0
- tradingcodex-0.1.0a1.dist-info/METADATA +204 -0
- tradingcodex-0.1.0a1.dist-info/RECORD +234 -0
- tradingcodex-0.1.0a1.dist-info/WHEEL +5 -0
- tradingcodex-0.1.0a1.dist-info/entry_points.txt +2 -0
- tradingcodex-0.1.0a1.dist-info/licenses/LICENSE +202 -0
- tradingcodex-0.1.0a1.dist-info/licenses/NOTICE +24 -0
- tradingcodex-0.1.0a1.dist-info/top_level.txt +4 -0
- tradingcodex_cli/__init__.py +1 -0
- tradingcodex_cli/__main__.py +120 -0
- tradingcodex_cli/generator.py +209 -0
- tradingcodex_cli/mcp_stdio.py +26 -0
- tradingcodex_cli/service_autostart.py +75 -0
- tradingcodex_cli/workspace.py +782 -0
- tradingcodex_service/__init__.py +3 -0
- tradingcodex_service/admin.py +7 -0
- tradingcodex_service/api.py +303 -0
- tradingcodex_service/asgi.py +6 -0
- tradingcodex_service/domain.py +1797 -0
- tradingcodex_service/mcp_http.py +59 -0
- tradingcodex_service/mcp_runtime.py +507 -0
- tradingcodex_service/settings.py +92 -0
- tradingcodex_service/templates/admin/base_site.html +35 -0
- tradingcodex_service/templates/admin/index.html +242 -0
- tradingcodex_service/templates/admin/login.html +56 -0
- tradingcodex_service/templates/web/activity.html +33 -0
- tradingcodex_service/templates/web/base.html +93 -0
- tradingcodex_service/templates/web/dashboard.html +99 -0
- tradingcodex_service/templates/web/fragments/role_inspector.html +66 -0
- tradingcodex_service/templates/web/fragments/starter_prompt.html +24 -0
- tradingcodex_service/templates/web/fragments/topology_canvas.html +79 -0
- tradingcodex_service/templates/web/harness.html +48 -0
- tradingcodex_service/templates/web/orders.html +77 -0
- tradingcodex_service/templates/web/policy.html +80 -0
- tradingcodex_service/templates/web/portfolio.html +61 -0
- tradingcodex_service/templates/web/research.html +52 -0
- tradingcodex_service/templates/web/starter_prompt.html +49 -0
- tradingcodex_service/urls.py +26 -0
- tradingcodex_service/web.py +243 -0
- tradingcodex_service/wsgi.py +6 -0
- workspace_templates/__init__.py +1 -0
- workspace_templates/modules/audit/files/.tradingcodex/audit/README.md +5 -0
- workspace_templates/modules/audit/files/trading/audit/.gitkeep +1 -0
- workspace_templates/modules/audit/module.json +16 -0
- workspace_templates/modules/codex-base/files/.codex/config.toml +356 -0
- workspace_templates/modules/codex-base/files/.codex/hooks/tradingcodex_hook.py +139 -0
- workspace_templates/modules/codex-base/files/.codex/hooks.json +105 -0
- workspace_templates/modules/codex-base/files/.codex/rules/tradingcodex.rules +50 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/capabilities.yaml +56 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/cli.py +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/config.yaml +68 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/integrations/openbb-mcp.safe-profile.md +61 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/policy-bindings.yaml +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/principals.yaml +17 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/roles.yaml +27 -0
- workspace_templates/modules/codex-base/files/AGENTS.md +46 -0
- workspace_templates/modules/codex-base/files/pyproject.toml +13 -0
- workspace_templates/modules/codex-base/files/tcx +7 -0
- workspace_templates/modules/codex-base/module.json +17 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/access-policies.yaml +39 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/restricted-list.yaml +6 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/approval_receipt.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/audit_event.schema.json +11 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/evidence_pack.schema.json +16 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/execution_result.schema.json +12 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/fundamental_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/news_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/order_intent.schema.json +30 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/portfolio_review.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/postmortem_report.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/risk_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/technical_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/thesis.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/valuation.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/scripts/validate-order-intent.py +15 -0
- workspace_templates/modules/enforcement-guardrails/module.json +19 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/execution-operator.toml +70 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/fundamental-analyst.toml +61 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/instrument-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/macro-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/news-analyst.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/portfolio-manager.toml +61 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/risk-manager.toml +64 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/technical-analyst.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/valuation-analyst.toml +58 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/head-manager.yaml +67 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/skill-change-proposals/.gitkeep +1 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/subagent-registry.yaml +56 -0
- workspace_templates/modules/fixed-subagents/module.json +23 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/guardrails.md +15 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/task-quality-checklist.md +35 -0
- workspace_templates/modules/guidance-guardrails/module.json +18 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/policies/information-barriers.yaml +211 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/secrets.md +9 -0
- workspace_templates/modules/information-barriers/files/trading/approvals/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/market-data/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/approved/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/draft/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/executed/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/rejected/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/fundamental/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/instrument/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/macro/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/news/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/policy/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/postmortem/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/risk/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/technical/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/valuation/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/research/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/module.json +19 -0
- workspace_templates/modules/paper-trading/files/.tradingcodex/mcp/adapters/paper-trading.py +4 -0
- workspace_templates/modules/paper-trading/module.json +16 -0
- workspace_templates/modules/postmortem/files/.tradingcodex/workflows/postmortem.yaml +12 -0
- workspace_templates/modules/postmortem/module.json +16 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/approve-order/SKILL.md +38 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/approve-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/collect-evidence/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/collect-evidence/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/create-order-intent/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/create-order-intent/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/execute-paper-order/SKILL.md +35 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/execute-paper-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/external-data-source-gate/SKILL.md +76 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/fundamental-analysis/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/fundamental-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/head-manager-interview/SKILL.md +81 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/head-manager-interview/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/head-manager-interview/references/investor-profile-reference.md +50 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/instrument-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/instrument-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/SKILL.md +100 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/macro-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/macro-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/SKILL.md +119 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/news-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/news-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/SKILL.md +290 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/policy-review/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/policy-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/portfolio-review/SKILL.md +44 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/portfolio-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/SKILL.md +31 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/review-risk/SKILL.md +45 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/review-risk/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/SKILL.md +126 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/SKILL.md +45 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/technical-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/technical-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/valuation-review/SKILL.md +47 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/valuation-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/mainagent/head-manager-interview.md +97 -0
- workspace_templates/modules/repo-skills/module.json +36 -0
- workspace_templates/modules/stub-execution/files/.tradingcodex/mcp/adapters/stub-execution.py +4 -0
- workspace_templates/modules/stub-execution/module.json +15 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/adapters/live-adapter.contract.md +25 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/enforcer/README.md +5 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/gateway/README.md +8 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/server.py +27 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/smoke-call.py +18 -0
- workspace_templates/modules/tradingcodex-mcp/module.json +24 -0
apps/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
apps/audit/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
apps/audit/admin.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from apps.audit.models import AuditEvent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@admin.register(AuditEvent)
|
|
7
|
+
class AuditEventAdmin(admin.ModelAdmin):
|
|
8
|
+
list_display = ("created_at", "actor_principal", "source", "action", "decision", "resource")
|
|
9
|
+
list_filter = ("source", "decision", "action")
|
|
10
|
+
search_fields = ("actor_principal", "action", "resource", "request_hash", "result_hash")
|
|
11
|
+
readonly_fields = ("created_at", "workspace_context", "request_hash", "result_hash")
|
apps/audit/apps.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2026-06-08 09:49
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='AuditEvent',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
19
|
+
('actor_principal', models.CharField(default='system', max_length=128)),
|
|
20
|
+
('source', models.CharField(default='service', max_length=32)),
|
|
21
|
+
('action', models.CharField(max_length=160)),
|
|
22
|
+
('resource', models.CharField(blank=True, max_length=255)),
|
|
23
|
+
('decision', models.CharField(default='recorded', max_length=32)),
|
|
24
|
+
('request_hash', models.CharField(blank=True, max_length=64)),
|
|
25
|
+
('result_hash', models.CharField(blank=True, max_length=64)),
|
|
26
|
+
('workspace_context', models.JSONField(blank=True, default=dict)),
|
|
27
|
+
('payload', models.JSONField(blank=True, default=dict)),
|
|
28
|
+
],
|
|
29
|
+
options={
|
|
30
|
+
'verbose_name': 'Audit event',
|
|
31
|
+
'verbose_name_plural': 'Audit events',
|
|
32
|
+
'ordering': ['-created_at', '-id'],
|
|
33
|
+
},
|
|
34
|
+
),
|
|
35
|
+
]
|
|
File without changes
|
apps/audit/models.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuditEvent(models.Model):
|
|
5
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
6
|
+
actor_principal = models.CharField(max_length=128, default="system")
|
|
7
|
+
source = models.CharField(max_length=32, default="service")
|
|
8
|
+
action = models.CharField(max_length=160)
|
|
9
|
+
resource = models.CharField(max_length=255, blank=True)
|
|
10
|
+
decision = models.CharField(max_length=32, default="recorded")
|
|
11
|
+
request_hash = models.CharField(max_length=64, blank=True)
|
|
12
|
+
result_hash = models.CharField(max_length=64, blank=True)
|
|
13
|
+
workspace_context = models.JSONField(default=dict, blank=True)
|
|
14
|
+
payload = models.JSONField(default=dict, blank=True)
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
ordering = ["-created_at", "-id"]
|
|
18
|
+
verbose_name = "Audit event"
|
|
19
|
+
verbose_name_plural = "Audit events"
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return f"{self.created_at:%Y-%m-%d %H:%M:%S} {self.action}"
|
apps/harness/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
apps/harness/admin.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from apps.harness.models import RoleSkillAssignment, SkillProposal, WorkspaceContext
|
|
4
|
+
from apps.harness.services import (
|
|
5
|
+
apply_skill_proposals,
|
|
6
|
+
approve_skill_proposals,
|
|
7
|
+
reject_skill_proposals,
|
|
8
|
+
set_role_skill_assignments_enabled,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@admin.register(WorkspaceContext)
|
|
13
|
+
class WorkspaceContextAdmin(admin.ModelAdmin):
|
|
14
|
+
list_display = ("project_name", "path_hash", "git_branch", "last_seen_at")
|
|
15
|
+
search_fields = ("project_name", "path", "path_hash", "git_remote", "git_branch")
|
|
16
|
+
readonly_fields = ("path_hash", "created_at", "last_seen_at")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@admin.register(RoleSkillAssignment)
|
|
20
|
+
class RoleSkillAssignmentAdmin(admin.ModelAdmin):
|
|
21
|
+
list_display = ("role", "skill", "enabled", "source")
|
|
22
|
+
list_filter = ("role", "enabled", "source")
|
|
23
|
+
search_fields = ("role", "skill")
|
|
24
|
+
actions = ["enable_assignments", "disable_assignments"]
|
|
25
|
+
|
|
26
|
+
@admin.action(description="Enable selected role skill assignments")
|
|
27
|
+
def enable_assignments(self, request, queryset):
|
|
28
|
+
set_role_skill_assignments_enabled(queryset, True, str(request.user or "admin"))
|
|
29
|
+
|
|
30
|
+
@admin.action(description="Disable selected role skill assignments")
|
|
31
|
+
def disable_assignments(self, request, queryset):
|
|
32
|
+
set_role_skill_assignments_enabled(queryset, False, str(request.user or "admin"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@admin.register(SkillProposal)
|
|
36
|
+
class SkillProposalAdmin(admin.ModelAdmin):
|
|
37
|
+
list_display = ("proposal_id", "type", "target", "skill", "status", "execution_sensitive", "created_at")
|
|
38
|
+
list_filter = ("type", "status", "execution_sensitive", "target")
|
|
39
|
+
search_fields = ("proposal_id", "target", "skill", "approved_by")
|
|
40
|
+
actions = ["approve_proposals", "apply_proposals", "reject_proposals"]
|
|
41
|
+
|
|
42
|
+
@admin.action(description="Approve selected skill proposals")
|
|
43
|
+
def approve_proposals(self, request, queryset):
|
|
44
|
+
approve_skill_proposals(queryset, str(request.user or "admin"))
|
|
45
|
+
|
|
46
|
+
@admin.action(description="Apply selected approved skill proposals")
|
|
47
|
+
def apply_proposals(self, request, queryset):
|
|
48
|
+
apply_skill_proposals(queryset, str(request.user or "admin"))
|
|
49
|
+
|
|
50
|
+
@admin.action(description="Reject selected skill proposals")
|
|
51
|
+
def reject_proposals(self, request, queryset):
|
|
52
|
+
reject_skill_proposals(queryset, str(request.user or "admin"))
|
apps/harness/apps.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2026-06-08 09:49
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='SkillProposal',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('proposal_id', models.CharField(max_length=220, unique=True)),
|
|
19
|
+
('type', models.CharField(max_length=32)),
|
|
20
|
+
('target', models.CharField(max_length=128)),
|
|
21
|
+
('skill', models.CharField(max_length=160)),
|
|
22
|
+
('status', models.CharField(default='proposed', max_length=32)),
|
|
23
|
+
('approved_by', models.CharField(blank=True, max_length=128)),
|
|
24
|
+
('execution_sensitive', models.BooleanField(default=False)),
|
|
25
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
26
|
+
('applied_at', models.DateTimeField(blank=True, null=True)),
|
|
27
|
+
],
|
|
28
|
+
options={
|
|
29
|
+
'verbose_name': 'Skill proposal',
|
|
30
|
+
'verbose_name_plural': 'Skill proposals',
|
|
31
|
+
'ordering': ['-created_at', '-id'],
|
|
32
|
+
},
|
|
33
|
+
),
|
|
34
|
+
migrations.CreateModel(
|
|
35
|
+
name='WorkspaceContext',
|
|
36
|
+
fields=[
|
|
37
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
38
|
+
('path_hash', models.CharField(max_length=64, unique=True)),
|
|
39
|
+
('project_name', models.CharField(max_length=180)),
|
|
40
|
+
('path', models.CharField(max_length=1024)),
|
|
41
|
+
('git_remote', models.CharField(blank=True, max_length=512)),
|
|
42
|
+
('git_branch', models.CharField(blank=True, max_length=180)),
|
|
43
|
+
('metadata', models.JSONField(blank=True, default=dict)),
|
|
44
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
45
|
+
('last_seen_at', models.DateTimeField(auto_now=True)),
|
|
46
|
+
],
|
|
47
|
+
options={
|
|
48
|
+
'verbose_name': 'Workspace context',
|
|
49
|
+
'verbose_name_plural': 'Workspace contexts',
|
|
50
|
+
'ordering': ['project_name', 'id'],
|
|
51
|
+
},
|
|
52
|
+
),
|
|
53
|
+
migrations.CreateModel(
|
|
54
|
+
name='RoleSkillAssignment',
|
|
55
|
+
fields=[
|
|
56
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
57
|
+
('role', models.CharField(max_length=128)),
|
|
58
|
+
('skill', models.CharField(max_length=160)),
|
|
59
|
+
('enabled', models.BooleanField(default=True)),
|
|
60
|
+
('source', models.CharField(default='bootstrap', max_length=64)),
|
|
61
|
+
],
|
|
62
|
+
options={
|
|
63
|
+
'verbose_name': 'Role skill assignment',
|
|
64
|
+
'verbose_name_plural': 'Role skill assignments',
|
|
65
|
+
'unique_together': {('role', 'skill')},
|
|
66
|
+
},
|
|
67
|
+
),
|
|
68
|
+
]
|
|
File without changes
|
apps/harness/models.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WorkspaceContext(models.Model):
|
|
5
|
+
path_hash = models.CharField(max_length=64, unique=True)
|
|
6
|
+
project_name = models.CharField(max_length=180)
|
|
7
|
+
path = models.CharField(max_length=1024)
|
|
8
|
+
git_remote = models.CharField(max_length=512, blank=True)
|
|
9
|
+
git_branch = models.CharField(max_length=180, blank=True)
|
|
10
|
+
metadata = models.JSONField(default=dict, blank=True)
|
|
11
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
12
|
+
last_seen_at = models.DateTimeField(auto_now=True)
|
|
13
|
+
|
|
14
|
+
class Meta:
|
|
15
|
+
ordering = ["project_name", "id"]
|
|
16
|
+
verbose_name = "Workspace context"
|
|
17
|
+
verbose_name_plural = "Workspace contexts"
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
return f"{self.project_name} {self.path_hash[:12]}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RoleSkillAssignment(models.Model):
|
|
24
|
+
role = models.CharField(max_length=128)
|
|
25
|
+
skill = models.CharField(max_length=160)
|
|
26
|
+
enabled = models.BooleanField(default=True)
|
|
27
|
+
source = models.CharField(max_length=64, default="bootstrap")
|
|
28
|
+
|
|
29
|
+
class Meta:
|
|
30
|
+
unique_together = [("role", "skill")]
|
|
31
|
+
verbose_name = "Role skill assignment"
|
|
32
|
+
verbose_name_plural = "Role skill assignments"
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
return f"{self.role}: {self.skill}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SkillProposal(models.Model):
|
|
39
|
+
proposal_id = models.CharField(max_length=220, unique=True)
|
|
40
|
+
type = models.CharField(max_length=32)
|
|
41
|
+
target = models.CharField(max_length=128)
|
|
42
|
+
skill = models.CharField(max_length=160)
|
|
43
|
+
status = models.CharField(max_length=32, default="proposed")
|
|
44
|
+
approved_by = models.CharField(max_length=128, blank=True)
|
|
45
|
+
execution_sensitive = models.BooleanField(default=False)
|
|
46
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
47
|
+
applied_at = models.DateTimeField(null=True, blank=True)
|
|
48
|
+
|
|
49
|
+
class Meta:
|
|
50
|
+
ordering = ["-created_at", "-id"]
|
|
51
|
+
verbose_name = "Skill proposal"
|
|
52
|
+
verbose_name_plural = "Skill proposals"
|
|
53
|
+
|
|
54
|
+
def __str__(self) -> str:
|
|
55
|
+
return self.proposal_id
|
apps/harness/services.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from django.db.models import QuerySet
|
|
6
|
+
from django.utils import timezone
|
|
7
|
+
|
|
8
|
+
from apps.harness.models import RoleSkillAssignment, SkillProposal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def set_role_skill_assignments_enabled(queryset: QuerySet[RoleSkillAssignment], enabled: bool, actor: str = "admin") -> int:
|
|
12
|
+
count = queryset.update(enabled=enabled)
|
|
13
|
+
_audit("role_skill_assignment.enabled" if enabled else "role_skill_assignment.disabled", {"count": count}, actor)
|
|
14
|
+
return count
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def approve_skill_proposals(queryset: QuerySet[SkillProposal], actor: str = "admin") -> int:
|
|
18
|
+
count = queryset.update(status="approved", approved_by=actor)
|
|
19
|
+
_audit("skill_proposal.approved", {"count": count}, actor)
|
|
20
|
+
return count
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def apply_skill_proposals(queryset: QuerySet[SkillProposal], actor: str = "admin") -> int:
|
|
24
|
+
applied = 0
|
|
25
|
+
for proposal in queryset.filter(status__in=["approved", "proposed"]):
|
|
26
|
+
RoleSkillAssignment.objects.update_or_create(
|
|
27
|
+
role=proposal.target,
|
|
28
|
+
skill=proposal.skill,
|
|
29
|
+
defaults={"enabled": True, "source": f"proposal:{proposal.proposal_id}"},
|
|
30
|
+
)
|
|
31
|
+
proposal.status = "applied"
|
|
32
|
+
proposal.approved_by = proposal.approved_by or actor
|
|
33
|
+
proposal.applied_at = timezone.now()
|
|
34
|
+
proposal.save(update_fields=["status", "approved_by", "applied_at"])
|
|
35
|
+
applied += 1
|
|
36
|
+
_audit("skill_proposal.applied", {"count": applied}, actor)
|
|
37
|
+
return applied
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def reject_skill_proposals(queryset: QuerySet[SkillProposal], actor: str = "admin") -> int:
|
|
41
|
+
count = queryset.update(status="rejected")
|
|
42
|
+
_audit("skill_proposal.rejected", {"count": count}, actor)
|
|
43
|
+
return count
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _audit(action: str, payload: dict[str, Any], actor: str) -> None:
|
|
47
|
+
from tradingcodex_service.domain import write_audit_event_if_available
|
|
48
|
+
|
|
49
|
+
write_audit_event_if_available(None, actor, "admin", {"type": action, "payload": payload})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from django import template
|
|
6
|
+
from django.urls import NoReverseMatch, reverse
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
|
|
9
|
+
from tradingcodex_service.domain import tradingcodex_db_path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
register = template.Library()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _count(model: Any, **filters: Any) -> int:
|
|
16
|
+
try:
|
|
17
|
+
return model.objects.filter(**filters).count() if filters else model.objects.count()
|
|
18
|
+
except Exception:
|
|
19
|
+
return 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _admin_url(name: str) -> str:
|
|
23
|
+
try:
|
|
24
|
+
return reverse(f"admin:{name}_changelist")
|
|
25
|
+
except NoReverseMatch:
|
|
26
|
+
return "#"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register.simple_tag
|
|
30
|
+
def tc_admin_overview() -> dict[str, Any]:
|
|
31
|
+
from apps.audit.models import AuditEvent
|
|
32
|
+
from apps.harness.models import SkillProposal, WorkspaceContext
|
|
33
|
+
from apps.integrations.models import AdapterDefinition
|
|
34
|
+
from apps.mcp.models import McpToolCall, McpToolDefinition
|
|
35
|
+
from apps.orders.models import ApprovalReceipt, ExecutionResult, OrderIntent
|
|
36
|
+
from apps.policy.models import PolicyDecision, RestrictedSymbol
|
|
37
|
+
from apps.portfolio.models import PortfolioSnapshot
|
|
38
|
+
from apps.research.models import ResearchArtifact, SourceSnapshot
|
|
39
|
+
|
|
40
|
+
latest_snapshot = PortfolioSnapshot.objects.order_by("-created_at", "-id").first()
|
|
41
|
+
portfolio_payload = latest_snapshot.payload if latest_snapshot and isinstance(latest_snapshot.payload, dict) else {}
|
|
42
|
+
positions = portfolio_payload.get("positions") if isinstance(portfolio_payload.get("positions"), dict) else {}
|
|
43
|
+
cash_krw = portfolio_payload.get("cash_krw")
|
|
44
|
+
if cash_krw is None:
|
|
45
|
+
cash_krw = 0
|
|
46
|
+
|
|
47
|
+
recent_calls = McpToolCall.objects.order_by("-created_at", "-id")[:6]
|
|
48
|
+
recent_audit = AuditEvent.objects.order_by("-created_at", "-id")[:6]
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
"generated_at": timezone.now(),
|
|
52
|
+
"db_path": str(tradingcodex_db_path()),
|
|
53
|
+
"workspace_count": _count(WorkspaceContext),
|
|
54
|
+
"research_count": _count(ResearchArtifact),
|
|
55
|
+
"source_snapshot_count": _count(SourceSnapshot),
|
|
56
|
+
"pending_skill_proposals": _count(SkillProposal, status="proposed"),
|
|
57
|
+
"draft_orders": _count(OrderIntent),
|
|
58
|
+
"valid_approvals": _count(ApprovalReceipt, valid=True),
|
|
59
|
+
"executions": _count(ExecutionResult),
|
|
60
|
+
"policy_denies": _count(PolicyDecision, decision="deny"),
|
|
61
|
+
"restricted_symbols": _count(RestrictedSymbol, active=True),
|
|
62
|
+
"mcp_tools_enabled": _count(McpToolDefinition, enabled=True),
|
|
63
|
+
"mcp_tools_total": _count(McpToolDefinition),
|
|
64
|
+
"mcp_errors": _count(McpToolCall, status="error"),
|
|
65
|
+
"adapters_enabled": _count(AdapterDefinition, enabled=True),
|
|
66
|
+
"live_adapters_enabled": _count(AdapterDefinition, enabled=True, live=True),
|
|
67
|
+
"cash_krw": cash_krw,
|
|
68
|
+
"positions_count": len(positions),
|
|
69
|
+
"latest_snapshot": latest_snapshot,
|
|
70
|
+
"recent_calls": recent_calls,
|
|
71
|
+
"recent_audit": recent_audit,
|
|
72
|
+
"quick_links": [
|
|
73
|
+
{"label": "Research Memory", "url": _admin_url("research_researchartifact"), "kind": "Research"},
|
|
74
|
+
{"label": "Order Intents", "url": _admin_url("orders_orderintent"), "kind": "Orders"},
|
|
75
|
+
{"label": "Approvals", "url": _admin_url("orders_approvalreceipt"), "kind": "Risk"},
|
|
76
|
+
{"label": "Executions", "url": _admin_url("orders_executionresult"), "kind": "Execution"},
|
|
77
|
+
{"label": "Paper Portfolio", "url": _admin_url("portfolio_portfoliosnapshot"), "kind": "Portfolio"},
|
|
78
|
+
{"label": "MCP Calls", "url": _admin_url("mcp_mcptoolcall"), "kind": "MCP"},
|
|
79
|
+
{"label": "Audit Events", "url": _admin_url("audit_auditevent"), "kind": "Audit"},
|
|
80
|
+
{"label": "Workspace Contexts", "url": _admin_url("harness_workspacecontext"), "kind": "Harness"},
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@register.filter
|
|
86
|
+
def tc_app_purpose(app_label: str) -> str:
|
|
87
|
+
return {
|
|
88
|
+
"auth": "Admin users, groups, and staff access.",
|
|
89
|
+
"harness": "Workspace provenance, role skills, and skill proposals.",
|
|
90
|
+
"research": "DB-canonical markdown research, source snapshots, and evidence packs.",
|
|
91
|
+
"orders": "Order intents, approval receipts, and paper/stub execution results.",
|
|
92
|
+
"portfolio": "Central paper portfolio snapshots, cash, and positions.",
|
|
93
|
+
"policy": "Principals, capability allowlists, restricted list, and policy decisions.",
|
|
94
|
+
"mcp": "Tool registry and MCP call ledger.",
|
|
95
|
+
"audit": "Append-only operational and policy event history.",
|
|
96
|
+
"integrations": "Read-only data and execution adapter definitions.",
|
|
97
|
+
"universes": "Enabled investment universe plugins and defaults.",
|
|
98
|
+
"workflows": "Workflow runs, handoffs, readiness labels, and artifacts.",
|
|
99
|
+
}.get(app_label, "Operational records and configuration.")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@register.filter
|
|
103
|
+
def tc_status_class(value: Any) -> str:
|
|
104
|
+
text = str(value).lower()
|
|
105
|
+
if text in {"ok", "allow", "accepted", "approved", "enabled", "filled", "valid", "true"}:
|
|
106
|
+
return "tc-status-good"
|
|
107
|
+
if text in {"deny", "denied", "rejected", "error", "blocked", "disabled", "false"}:
|
|
108
|
+
return "tc-status-bad"
|
|
109
|
+
if text in {"proposed", "pending", "recorded", "stubbed", "research-only"}:
|
|
110
|
+
return "tc-status-warn"
|
|
111
|
+
return "tc-status-neutral"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from apps.integrations.models import AdapterDefinition
|
|
4
|
+
from apps.integrations.services import disable_adapters, disable_live_adapters, enable_non_live_adapters
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@admin.register(AdapterDefinition)
|
|
8
|
+
class AdapterDefinitionAdmin(admin.ModelAdmin):
|
|
9
|
+
list_display = ("adapter_id", "kind", "enabled", "live")
|
|
10
|
+
list_filter = ("enabled", "live", "kind")
|
|
11
|
+
search_fields = ("adapter_id", "kind")
|
|
12
|
+
actions = ["enable_non_live_adapters", "disable_adapters", "disable_live_adapters"]
|
|
13
|
+
|
|
14
|
+
@admin.action(description="Enable selected non-live adapters")
|
|
15
|
+
def enable_non_live_adapters(self, request, queryset):
|
|
16
|
+
enable_non_live_adapters(queryset, str(request.user or "admin"))
|
|
17
|
+
|
|
18
|
+
@admin.action(description="Disable selected adapters")
|
|
19
|
+
def disable_adapters(self, request, queryset):
|
|
20
|
+
disable_adapters(queryset, str(request.user or "admin"))
|
|
21
|
+
|
|
22
|
+
@admin.action(description="Disable all selected live adapters")
|
|
23
|
+
def disable_live_adapters(self, request, queryset):
|
|
24
|
+
disable_live_adapters(queryset, str(request.user or "admin"))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2026-06-08 09:49
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='AdapterDefinition',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('adapter_id', models.CharField(max_length=120, unique=True)),
|
|
19
|
+
('kind', models.CharField(default='execution', max_length=64)),
|
|
20
|
+
('enabled', models.BooleanField(default=False)),
|
|
21
|
+
('live', models.BooleanField(default=False)),
|
|
22
|
+
('config', models.JSONField(blank=True, default=dict)),
|
|
23
|
+
],
|
|
24
|
+
options={
|
|
25
|
+
'verbose_name': 'Adapter definition',
|
|
26
|
+
'verbose_name_plural': 'Adapter definitions',
|
|
27
|
+
},
|
|
28
|
+
),
|
|
29
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AdapterDefinition(models.Model):
|
|
5
|
+
adapter_id = models.CharField(max_length=120, unique=True)
|
|
6
|
+
kind = models.CharField(max_length=64, default="execution")
|
|
7
|
+
enabled = models.BooleanField(default=False)
|
|
8
|
+
live = models.BooleanField(default=False)
|
|
9
|
+
config = models.JSONField(default=dict, blank=True)
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
verbose_name = "Adapter definition"
|
|
13
|
+
verbose_name_plural = "Adapter definitions"
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return self.adapter_id
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from django.db.models import QuerySet
|
|
6
|
+
|
|
7
|
+
from apps.integrations.models import AdapterDefinition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def enable_non_live_adapters(queryset: QuerySet[AdapterDefinition], actor: str = "admin") -> int:
|
|
11
|
+
count = queryset.filter(live=False).update(enabled=True)
|
|
12
|
+
_audit("adapter.enabled_non_live", {"count": count}, actor)
|
|
13
|
+
return count
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def disable_adapters(queryset: QuerySet[AdapterDefinition], actor: str = "admin") -> int:
|
|
17
|
+
count = queryset.update(enabled=False)
|
|
18
|
+
_audit("adapter.disabled", {"count": count}, actor)
|
|
19
|
+
return count
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def disable_live_adapters(queryset: QuerySet[AdapterDefinition], actor: str = "admin") -> int:
|
|
23
|
+
count = queryset.filter(live=True).update(enabled=False)
|
|
24
|
+
_audit("adapter.live_disabled", {"count": count}, actor)
|
|
25
|
+
return count
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _audit(action: str, payload: dict[str, Any], actor: str) -> None:
|
|
29
|
+
from tradingcodex_service.domain import write_audit_event_if_available
|
|
30
|
+
|
|
31
|
+
write_audit_event_if_available(None, actor, "admin", {"type": action, "payload": payload})
|
apps/mcp/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
apps/mcp/admin.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from apps.mcp.models import McpToolCall, McpToolDefinition
|
|
4
|
+
from apps.mcp.services import set_mcp_tools_enabled, sync_builtin_mcp_registry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@admin.register(McpToolDefinition)
|
|
8
|
+
class McpToolDefinitionAdmin(admin.ModelAdmin):
|
|
9
|
+
list_display = ("name", "category", "risk_level", "requires_approval", "audit_required", "experimental", "enabled", "updated_at")
|
|
10
|
+
list_filter = ("enabled", "category", "risk_level", "requires_approval", "audit_required", "experimental")
|
|
11
|
+
search_fields = ("name", "description", "capability_required")
|
|
12
|
+
readonly_fields = ("updated_at",)
|
|
13
|
+
actions = ["enable_tools", "disable_tools", "sync_builtin_tools"]
|
|
14
|
+
|
|
15
|
+
@admin.action(description="Enable selected MCP tools")
|
|
16
|
+
def enable_tools(self, request, queryset):
|
|
17
|
+
set_mcp_tools_enabled(queryset, True, str(request.user or "admin"))
|
|
18
|
+
|
|
19
|
+
@admin.action(description="Disable selected MCP tools")
|
|
20
|
+
def disable_tools(self, request, queryset):
|
|
21
|
+
set_mcp_tools_enabled(queryset, False, str(request.user or "admin"))
|
|
22
|
+
|
|
23
|
+
@admin.action(description="Sync built-in MCP tool registry")
|
|
24
|
+
def sync_builtin_tools(self, request, queryset):
|
|
25
|
+
sync_builtin_mcp_registry(str(request.user or "admin"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@admin.register(McpToolCall)
|
|
29
|
+
class McpToolCallAdmin(admin.ModelAdmin):
|
|
30
|
+
list_display = ("created_at", "tool_name", "principal_id", "status", "duration_ms")
|
|
31
|
+
list_filter = ("tool_name", "status", "principal_id")
|
|
32
|
+
search_fields = ("tool_name", "principal_id", "request_hash", "result_hash", "error")
|
|
33
|
+
readonly_fields = ("created_at", "tool_name", "principal_id", "status", "request", "response", "workspace_context", "request_hash", "result_hash", "error", "duration_ms")
|