create-workframe 0.1.12 → 0.1.14
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.
- package/README.md +1 -1
- package/bin/create-workframe.js +2 -2
- package/bin/workframe.js +130 -2
- package/docs/security.md +2 -2
- package/package.json +1 -1
- package/scripts/bundle-workframe-ui.mjs +9 -0
- package/scripts/sync-canonical-to-package.mjs +1 -0
- package/scripts/verify-public-deploy.sh +65 -3
- package/workframe-api/action_proxy.py +23 -17
- package/workframe-api/activity_feed.py +798 -0
- package/workframe-api/api_meta.py +157 -0
- package/workframe-api/auth_gate.py +566 -0
- package/workframe-api/avatar_registry.py +343 -0
- package/workframe-api/broker_audit.py +164 -0
- package/workframe-api/cell_authority.py +231 -0
- package/workframe-api/chat_bind.py +319 -0
- package/workframe-api/chat_sessions.py +509 -0
- package/workframe-api/chat_stream.py +694 -0
- package/workframe-api/cleanup_dogfood_smoke.py +252 -0
- package/workframe-api/credential_broker.py +162 -0
- package/workframe-api/credential_resolve.py +202 -0
- package/workframe-api/credential_store.py +245 -0
- package/workframe-api/crew_registry.py +250 -0
- package/workframe-api/db_schema.py +755 -0
- package/workframe-api/docker_gateway.py +166 -0
- package/workframe-api/doctor_runtime.py +109 -0
- package/workframe-api/domain/__init__.py +47 -0
- package/workframe-api/domain/entities.py +252 -0
- package/workframe-api/domain/schema/workframe-domain.schema.json +278 -0
- package/workframe-api/egress_policy.py +42 -0
- package/workframe-api/handler_modules/__init__.py +17 -0
- package/workframe-api/handler_modules/handler_admin.py +542 -0
- package/workframe-api/handler_modules/handler_auth.py +512 -0
- package/workframe-api/handler_modules/handler_chat.py +641 -0
- package/workframe-api/handler_modules/handler_install.py +178 -0
- package/workframe-api/handler_modules/handler_provider.py +325 -0
- package/workframe-api/handler_modules/handler_workspace.py +1420 -0
- package/workframe-api/health_monitor.py +80 -0
- package/workframe-api/hermes_admin.py +756 -0
- package/workframe-api/hermes_profiles.py +1328 -0
- package/workframe-api/install_api.py +123 -0
- package/workframe-api/kanban_cron.py +473 -0
- package/workframe-api/lane_bindings.py +423 -0
- package/workframe-api/llm_proxy.py +17 -43
- package/workframe-api/mention_helpers.py +180 -0
- package/workframe-api/mention_invoke.py +431 -0
- package/workframe-api/model_surface.py +1139 -0
- package/workframe-api/oauth_pending.py +85 -0
- package/workframe-api/oauth_redirect.py +381 -0
- package/workframe-api/package.json +2 -2
- package/workframe-api/profile_api_lifecycle.py +66 -0
- package/workframe-api/profile_gateway.py +436 -0
- package/workframe-api/provider_bindings.py +673 -0
- package/workframe-api/provider_bootstrap.py +347 -0
- package/workframe-api/provider_catalog.py +180 -0
- package/workframe-api/rooms.py +1613 -0
- package/workframe-api/route_registry.py +331 -191
- package/workframe-api/run-typecheck.mjs +2 -1
- package/workframe-api/run_authority.py +287 -0
- package/workframe-api/run_ledger.py +470 -0
- package/workframe-api/run_surface_wiring.py +344 -0
- package/workframe-api/runtime_cohort.py +752 -0
- package/workframe-api/runtime_tokens.py +127 -0
- package/workframe-api/server.py +1453 -19513
- package/workframe-api/snapshot_feed.py +49 -0
- package/workframe-api/stack_config.py +33 -1
- package/workframe-api/supervisor_client.py +154 -0
- package/workframe-api/test_api_meta_build_stamp.py +34 -0
- package/workframe-api/test_cell_authority.py +79 -0
- package/workframe-api/test_chat_bind.py +48 -0
- package/workframe-api/test_credential_lease_matrix.py +171 -0
- package/workframe-api/test_credential_resolve.py +51 -0
- package/workframe-api/test_credential_store.py +27 -0
- package/workframe-api/test_dogfood_flows.py +346 -0
- package/workframe-api/test_domain_entities.py +152 -0
- package/workframe-api/test_exception_hygiene.py +12 -0
- package/workframe-api/test_hermes_admin.py +72 -0
- package/workframe-api/test_install_wizard_resume.py +78 -0
- package/workframe-api/test_local_bootstrap.py +67 -0
- package/workframe-api/test_mention_helpers.py +35 -0
- package/workframe-api/test_mention_invoke.py +26 -0
- package/workframe-api/test_model_surface_consistency.py +1 -0
- package/workframe-api/test_oauth_pending.py +41 -0
- package/workframe-api/test_provider_bindings.py +52 -0
- package/workframe-api/test_provider_catalog.py +28 -0
- package/workframe-api/test_route_registry.py +171 -35
- package/workframe-api/test_run_authority.py +112 -0
- package/workframe-api/test_run_ledger.py +157 -0
- package/workframe-api/test_run_surface_wiring.py +130 -0
- package/workframe-api/test_runtime_cohort.py +85 -0
- package/workframe-api/test_secure_mode_docker_boundary.py +39 -0
- package/workframe-api/test_server_reexports.py +99 -0
- package/workframe-api/test_stack_config_install_smtp.py +7 -0
- package/workframe-api/turn_credentials.py +28 -13
- package/workframe-api/turn_overlay.py +715 -0
- package/workframe-api/updates.py +16 -1
- package/workframe-api/user_prefs.py +387 -0
- package/workframe-api/wf032_extract_remaining_handlers.py +417 -0
- package/workframe-api/workspace_bootstrap.py +536 -0
- package/workframe-api/workspace_files.py +344 -0
- package/workframe-api/workspace_messaging.py +206 -0
- package/workframe-api/zk_auth.py +3 -2
- package/workframe-supervisor/server.py +10 -1
- package/workframe-supervisor/test_supervisor_negative.py +75 -0
- package/workframe-ui/public/assets/{arc-B0OFRGmJ.js → arc-BT9iZUSd.js} +1 -1
- package/workframe-ui/public/assets/architecture-7EHR7CIX-m1aPUQ_r.js +1 -0
- package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-CaEDd3np.js} +1 -1
- package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-DHJhMrIS.js} +1 -1
- package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-Cdyz9hIV.js} +1 -1
- package/workframe-ui/public/assets/channel-3M69ekE5.js +1 -0
- package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-BbZx76LX.js} +1 -1
- package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DcSxjTIP.js} +1 -1
- package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-B7F-kTY1.js} +1 -1
- package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-BwwEiRTK.js} +1 -1
- package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-DAi_NksX.js} +1 -1
- package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-BIaF4XCN.js} +1 -1
- package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-DjYSk1dG.js} +1 -1
- package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-_twnyfgV.js} +1 -1
- package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CkNmdy5s.js} +2 -2
- package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CjLqd-gl.js} +1 -1
- package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-_6oNqFjj.js} +1 -1
- package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-CeO9Am_D.js} +1 -1
- package/workframe-ui/public/assets/chunk-LZXEDZCA-sRM4YeIe.js +2 -0
- package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-CH8tMb21.js} +1 -1
- package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-DAI1bsII.js} +1 -1
- package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-B2e-5a2G.js} +1 -1
- package/workframe-ui/public/assets/chunk-QZHKN3VN-BMl9ed8P.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-C08HH6BU.js +1 -0
- package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-DM2-0q37.js} +1 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BFcOE4Aq.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BFcOE4Aq.js +1 -0
- package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-BnbbewLv.js} +1 -1
- package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-C4zmB1Vt.js} +1 -1
- package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-n-BJmsiC.js} +1 -1
- package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-BzQGQnZi.js} +1 -1
- package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-Cx_De-wS.js} +1 -1
- package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-CIOI3Lok.js} +1 -1
- package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-DkRuPLWY.js} +1 -1
- package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-XhCT1Q-V.js} +1 -1
- package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-C3GwhDU1.js} +1 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-DIVBL6CF.js +1 -0
- package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-CLWH8z2e.js} +1 -1
- package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D4h66Vy3.js} +1 -1
- package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-IW5F-Q6c.js} +1 -1
- package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-BjNIbmRM.js} +1 -1
- package/workframe-ui/public/assets/index-DL2vJP4L.css +1 -0
- package/workframe-ui/public/assets/index-ElS_D59P.js +130 -0
- package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CcJspM79.js} +1 -1
- package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-DP4xR9jt.js} +1 -1
- package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-D4cZb21V.js} +1 -1
- package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-DsLuHkIJ.js} +1 -1
- package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Wu65nOsR.js} +1 -1
- package/workframe-ui/public/assets/{line-CgQsmU35.js → line-ByBDzRpy.js} +1 -1
- package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-DzXi2vnq.js} +1 -1
- package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-CaB-1Ckn.js} +2 -2
- package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-D7HTHC17.js} +3 -3
- package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BfKsC9aj.js} +1 -1
- package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-ClpU98TO.js} +1 -1
- package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-KeOyY_-u.js} +1 -1
- package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-CUpjnzbs.js} +1 -1
- package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-CJdPTszF.js} +1 -1
- package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-C9BpandZ.js} +1 -1
- package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-CHDZPwry.js} +1 -1
- package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-CMJJh91s.js} +1 -1
- package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-BsV4GppP.js} +1 -1
- package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-WHks82Up.js} +1 -1
- package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-eSX9P8z0.js} +1 -1
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-FzMK4PkM.js +1 -0
- package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Dg1UkhGo.js} +1 -1
- package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-B7yGsBuj.js} +1 -1
- package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-j_G2oyQ_.js} +1 -1
- package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-BRrT0jqC.js} +1 -1
- package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-DEdUqez2.js} +1 -1
- package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-C-bBK9Bb.js} +1 -1
- package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-DhH5xuR2.js} +1 -1
- package/workframe-ui/public/index.html +11 -7
- package/workframe-ui/public/workframe-build.json +5 -0
- package/workframe-ui/public/assets/architecture-7EHR7CIX-Dwwi9yA0.js +0 -1
- package/workframe-ui/public/assets/channel-fNHbDpV4.js +0 -1
- package/workframe-ui/public/assets/chunk-LZXEDZCA-DMfdMUwz.js +0 -2
- package/workframe-ui/public/assets/chunk-QZHKN3VN-ewTgEQK8.js +0 -1
- package/workframe-ui/public/assets/chunk-WU5MYG2G-D4Tg-A0l.js +0 -1
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CFAgBpEl.js +0 -1
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CFAgBpEl.js +0 -1
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-BIJTP5k-.js +0 -1
- package/workframe-ui/public/assets/index-8CuZDEIG.css +0 -1
- package/workframe-ui/public/assets/index-xS9lFekI.js +0 -129
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CMLuNyeC.js +0 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"""Workframe-owned run ledger — runs, run_events, run_line_items (WF-NS-P2, WF-016)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sqlite3
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Mapping
|
|
13
|
+
|
|
14
|
+
from domain.entities import (
|
|
15
|
+
ActorType,
|
|
16
|
+
FundingSource,
|
|
17
|
+
Run,
|
|
18
|
+
RunEvent,
|
|
19
|
+
RunStatus,
|
|
20
|
+
RunSurface,
|
|
21
|
+
)
|
|
22
|
+
from run_authority import RunAuthorityDecision
|
|
23
|
+
|
|
24
|
+
DATA_DIR = Path(os.environ.get("WORKFRAME_API_DATA_DIR", "/app/data"))
|
|
25
|
+
WORKFRAME_DB = DATA_DIR / "workframe.db"
|
|
26
|
+
|
|
27
|
+
_SCHEMA_READY: set[str] = set()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _connect() -> sqlite3.Connection:
|
|
31
|
+
conn = sqlite3.connect(str(WORKFRAME_DB), timeout=10.0)
|
|
32
|
+
conn.row_factory = sqlite3.Row
|
|
33
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
34
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
35
|
+
return conn
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _utc_now() -> str:
|
|
39
|
+
return datetime.now(timezone.utc).isoformat()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _iso_ts() -> str:
|
|
43
|
+
return str(int(time.time()))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ensure_schema() -> None:
|
|
47
|
+
key = str(WORKFRAME_DB)
|
|
48
|
+
if key in _SCHEMA_READY:
|
|
49
|
+
return
|
|
50
|
+
conn = _connect()
|
|
51
|
+
try:
|
|
52
|
+
conn.executescript(
|
|
53
|
+
"""
|
|
54
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
55
|
+
version TEXT PRIMARY KEY,
|
|
56
|
+
description TEXT,
|
|
57
|
+
applied_at TEXT
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
61
|
+
run_id TEXT PRIMARY KEY,
|
|
62
|
+
workspace_id TEXT NOT NULL,
|
|
63
|
+
surface TEXT NOT NULL,
|
|
64
|
+
actor_type TEXT NOT NULL,
|
|
65
|
+
actor_id TEXT NOT NULL,
|
|
66
|
+
triggering_user_id TEXT NOT NULL,
|
|
67
|
+
agent_id TEXT NOT NULL DEFAULT '',
|
|
68
|
+
runtime_binding_id TEXT NOT NULL DEFAULT '',
|
|
69
|
+
status TEXT NOT NULL,
|
|
70
|
+
payer_user_id TEXT NOT NULL,
|
|
71
|
+
funding_source TEXT NOT NULL,
|
|
72
|
+
room_id TEXT DEFAULT NULL,
|
|
73
|
+
card_id TEXT DEFAULT NULL,
|
|
74
|
+
engine TEXT NOT NULL DEFAULT 'hermes',
|
|
75
|
+
runtime TEXT NOT NULL DEFAULT 'hermes_managed',
|
|
76
|
+
risk_tier TEXT NOT NULL DEFAULT 'low',
|
|
77
|
+
budget_usd REAL DEFAULT NULL,
|
|
78
|
+
deny_reason TEXT DEFAULT NULL,
|
|
79
|
+
profile_slug TEXT DEFAULT NULL,
|
|
80
|
+
provider TEXT DEFAULT NULL,
|
|
81
|
+
credential_ref_id TEXT DEFAULT NULL,
|
|
82
|
+
credential_scope TEXT DEFAULT NULL,
|
|
83
|
+
session_id TEXT DEFAULT NULL,
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
started_at TEXT DEFAULT NULL,
|
|
86
|
+
ended_at TEXT DEFAULT NULL
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_runs_workspace
|
|
90
|
+
ON runs(workspace_id, created_at DESC);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_runs_room
|
|
92
|
+
ON runs(room_id, created_at DESC)
|
|
93
|
+
WHERE room_id IS NOT NULL;
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_runs_status
|
|
95
|
+
ON runs(status, created_at DESC);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS run_events (
|
|
98
|
+
event_id TEXT PRIMARY KEY,
|
|
99
|
+
run_id TEXT NOT NULL,
|
|
100
|
+
event_type TEXT NOT NULL,
|
|
101
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
102
|
+
room_id TEXT DEFAULT NULL,
|
|
103
|
+
created_at TEXT NOT NULL,
|
|
104
|
+
FOREIGN KEY (run_id) REFERENCES runs (run_id)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_run_events_run
|
|
108
|
+
ON run_events(run_id, created_at ASC);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_run_events_room
|
|
110
|
+
ON run_events(room_id, created_at DESC)
|
|
111
|
+
WHERE room_id IS NOT NULL;
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS run_line_items (
|
|
114
|
+
line_item_id TEXT PRIMARY KEY,
|
|
115
|
+
run_id TEXT NOT NULL,
|
|
116
|
+
kind TEXT NOT NULL,
|
|
117
|
+
provider TEXT DEFAULT NULL,
|
|
118
|
+
payer_user_id TEXT NOT NULL,
|
|
119
|
+
funding_source TEXT NOT NULL,
|
|
120
|
+
amount_usd REAL DEFAULT NULL,
|
|
121
|
+
units REAL DEFAULT NULL,
|
|
122
|
+
unit_label TEXT DEFAULT NULL,
|
|
123
|
+
model TEXT DEFAULT NULL,
|
|
124
|
+
receipt_json TEXT NOT NULL DEFAULT '{}',
|
|
125
|
+
created_at TEXT NOT NULL,
|
|
126
|
+
FOREIGN KEY (run_id) REFERENCES runs (run_id)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_run_line_items_run
|
|
130
|
+
ON run_line_items(run_id, created_at ASC);
|
|
131
|
+
|
|
132
|
+
INSERT OR IGNORE INTO schema_migrations (version, description, applied_at)
|
|
133
|
+
VALUES ('14', 'runs, run_events, run_line_items ledger tables', datetime('now'));
|
|
134
|
+
"""
|
|
135
|
+
)
|
|
136
|
+
conn.commit()
|
|
137
|
+
finally:
|
|
138
|
+
conn.close()
|
|
139
|
+
_SCHEMA_READY.add(key)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _row_to_run(row: sqlite3.Row) -> Run:
|
|
143
|
+
return Run(
|
|
144
|
+
run_id=str(row["run_id"]),
|
|
145
|
+
workspace_id=str(row["workspace_id"]),
|
|
146
|
+
surface=RunSurface(str(row["surface"])),
|
|
147
|
+
actor_type=ActorType(str(row["actor_type"])),
|
|
148
|
+
actor_id=str(row["actor_id"]),
|
|
149
|
+
triggering_user_id=str(row["triggering_user_id"]),
|
|
150
|
+
agent_id=str(row["agent_id"] or ""),
|
|
151
|
+
runtime_binding_id=str(row["runtime_binding_id"] or ""),
|
|
152
|
+
status=RunStatus(str(row["status"])),
|
|
153
|
+
payer_user_id=str(row["payer_user_id"]),
|
|
154
|
+
funding_source=FundingSource(str(row["funding_source"])),
|
|
155
|
+
room_id=str(row["room_id"]) if row["room_id"] else None,
|
|
156
|
+
card_id=str(row["card_id"]) if row["card_id"] else None,
|
|
157
|
+
engine=str(row["engine"] or "hermes"),
|
|
158
|
+
runtime=str(row["runtime"] or "hermes_managed"),
|
|
159
|
+
risk_tier=str(row["risk_tier"] or "low"), # type: ignore[arg-type]
|
|
160
|
+
budget_usd=row["budget_usd"],
|
|
161
|
+
deny_reason=str(row["deny_reason"]) if row["deny_reason"] else None,
|
|
162
|
+
created_at=None,
|
|
163
|
+
started_at=None,
|
|
164
|
+
ended_at=None,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def insert_run(
|
|
169
|
+
conn: sqlite3.Connection,
|
|
170
|
+
*,
|
|
171
|
+
run_id: str,
|
|
172
|
+
workspace_id: str,
|
|
173
|
+
surface: RunSurface,
|
|
174
|
+
actor_type: ActorType,
|
|
175
|
+
actor_id: str,
|
|
176
|
+
triggering_user_id: str,
|
|
177
|
+
agent_id: str,
|
|
178
|
+
runtime_binding_id: str,
|
|
179
|
+
status: RunStatus,
|
|
180
|
+
payer_user_id: str,
|
|
181
|
+
funding_source: FundingSource,
|
|
182
|
+
room_id: str | None = None,
|
|
183
|
+
profile_slug: str | None = None,
|
|
184
|
+
provider: str | None = None,
|
|
185
|
+
deny_reason: str | None = None,
|
|
186
|
+
credential_ref_id: str | None = None,
|
|
187
|
+
credential_scope: str | None = None,
|
|
188
|
+
session_id: str | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
now = _utc_now()
|
|
191
|
+
conn.execute(
|
|
192
|
+
"""
|
|
193
|
+
INSERT INTO runs (
|
|
194
|
+
run_id, workspace_id, surface, actor_type, actor_id, triggering_user_id,
|
|
195
|
+
agent_id, runtime_binding_id, status, payer_user_id, funding_source,
|
|
196
|
+
room_id, profile_slug, provider, deny_reason, credential_ref_id,
|
|
197
|
+
credential_scope, session_id, created_at, started_at
|
|
198
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
199
|
+
""",
|
|
200
|
+
(
|
|
201
|
+
run_id,
|
|
202
|
+
workspace_id,
|
|
203
|
+
surface.value,
|
|
204
|
+
actor_type.value,
|
|
205
|
+
actor_id,
|
|
206
|
+
triggering_user_id,
|
|
207
|
+
agent_id,
|
|
208
|
+
runtime_binding_id,
|
|
209
|
+
status.value,
|
|
210
|
+
payer_user_id,
|
|
211
|
+
funding_source.value,
|
|
212
|
+
room_id,
|
|
213
|
+
profile_slug,
|
|
214
|
+
provider,
|
|
215
|
+
deny_reason,
|
|
216
|
+
credential_ref_id,
|
|
217
|
+
credential_scope,
|
|
218
|
+
session_id,
|
|
219
|
+
now,
|
|
220
|
+
now if status in (RunStatus.RUNNING, RunStatus.AUTHORIZED) else None,
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def insert_run_event(
|
|
226
|
+
conn: sqlite3.Connection,
|
|
227
|
+
*,
|
|
228
|
+
run_id: str,
|
|
229
|
+
event_type: str,
|
|
230
|
+
payload: Mapping[str, Any] | None = None,
|
|
231
|
+
room_id: str | None = None,
|
|
232
|
+
event_id: str | None = None,
|
|
233
|
+
) -> RunEvent:
|
|
234
|
+
eid = str(event_id or uuid.uuid4())
|
|
235
|
+
conn.execute(
|
|
236
|
+
"""
|
|
237
|
+
INSERT INTO run_events (event_id, run_id, event_type, payload_json, room_id, created_at)
|
|
238
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
239
|
+
""",
|
|
240
|
+
(eid, run_id, event_type, json.dumps(dict(payload or {})), room_id, _utc_now()),
|
|
241
|
+
)
|
|
242
|
+
return RunEvent(event_id=eid, run_id=run_id, event_type=event_type, payload=dict(payload or {}))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def update_run_status(
|
|
246
|
+
conn: sqlite3.Connection,
|
|
247
|
+
run_id: str,
|
|
248
|
+
status: RunStatus,
|
|
249
|
+
*,
|
|
250
|
+
deny_reason: str | None = None,
|
|
251
|
+
) -> None:
|
|
252
|
+
ended = status in (RunStatus.COMPLETED, RunStatus.FAILED, RunStatus.CANCELLED, RunStatus.DENIED)
|
|
253
|
+
conn.execute(
|
|
254
|
+
"""
|
|
255
|
+
UPDATE runs
|
|
256
|
+
SET status = ?, deny_reason = COALESCE(?, deny_reason), ended_at = ?
|
|
257
|
+
WHERE run_id = ?
|
|
258
|
+
""",
|
|
259
|
+
(status.value, deny_reason, _utc_now() if ended else None, run_id),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def insert_line_item(
|
|
264
|
+
conn: sqlite3.Connection,
|
|
265
|
+
*,
|
|
266
|
+
run_id: str,
|
|
267
|
+
kind: str,
|
|
268
|
+
payer_user_id: str,
|
|
269
|
+
funding_source: FundingSource,
|
|
270
|
+
provider: str | None = None,
|
|
271
|
+
amount_usd: float | None = None,
|
|
272
|
+
units: float | None = None,
|
|
273
|
+
unit_label: str | None = None,
|
|
274
|
+
model: str | None = None,
|
|
275
|
+
receipt: Mapping[str, Any] | None = None,
|
|
276
|
+
line_item_id: str | None = None,
|
|
277
|
+
) -> str:
|
|
278
|
+
lid = str(line_item_id or uuid.uuid4())
|
|
279
|
+
conn.execute(
|
|
280
|
+
"""
|
|
281
|
+
INSERT INTO run_line_items (
|
|
282
|
+
line_item_id, run_id, kind, provider, payer_user_id, funding_source,
|
|
283
|
+
amount_usd, units, unit_label, model, receipt_json, created_at
|
|
284
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
285
|
+
""",
|
|
286
|
+
(
|
|
287
|
+
lid,
|
|
288
|
+
run_id,
|
|
289
|
+
kind,
|
|
290
|
+
provider,
|
|
291
|
+
payer_user_id,
|
|
292
|
+
funding_source.value,
|
|
293
|
+
amount_usd,
|
|
294
|
+
units,
|
|
295
|
+
unit_label,
|
|
296
|
+
model,
|
|
297
|
+
json.dumps(dict(receipt or {})),
|
|
298
|
+
_utc_now(),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
return lid
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def record_authority_decision(
|
|
305
|
+
conn: sqlite3.Connection,
|
|
306
|
+
*,
|
|
307
|
+
run_id: str,
|
|
308
|
+
request_surface: RunSurface,
|
|
309
|
+
actor_type: ActorType,
|
|
310
|
+
actor_id: str,
|
|
311
|
+
triggering_user_id: str,
|
|
312
|
+
workspace_id: str,
|
|
313
|
+
agent_id: str,
|
|
314
|
+
runtime_binding_id: str,
|
|
315
|
+
profile_slug: str,
|
|
316
|
+
provider: str,
|
|
317
|
+
room_id: str | None,
|
|
318
|
+
session_id: str | None,
|
|
319
|
+
decision: RunAuthorityDecision,
|
|
320
|
+
) -> None:
|
|
321
|
+
status = RunStatus.AUTHORIZED if decision.allowed else RunStatus.DENIED
|
|
322
|
+
if decision.allowed:
|
|
323
|
+
status = RunStatus.RUNNING
|
|
324
|
+
insert_run(
|
|
325
|
+
conn,
|
|
326
|
+
run_id=run_id,
|
|
327
|
+
workspace_id=workspace_id,
|
|
328
|
+
surface=request_surface,
|
|
329
|
+
actor_type=actor_type,
|
|
330
|
+
actor_id=actor_id,
|
|
331
|
+
triggering_user_id=triggering_user_id,
|
|
332
|
+
agent_id=agent_id,
|
|
333
|
+
runtime_binding_id=runtime_binding_id,
|
|
334
|
+
status=status,
|
|
335
|
+
payer_user_id=decision.payer_user_id or triggering_user_id,
|
|
336
|
+
funding_source=decision.funding_source,
|
|
337
|
+
room_id=room_id,
|
|
338
|
+
profile_slug=profile_slug,
|
|
339
|
+
provider=provider,
|
|
340
|
+
deny_reason=decision.deny_reason,
|
|
341
|
+
credential_ref_id=decision.credential_ref_id,
|
|
342
|
+
credential_scope=decision.credential_scope,
|
|
343
|
+
session_id=session_id,
|
|
344
|
+
)
|
|
345
|
+
event_type = "run.authorized" if decision.allowed else "run.denied"
|
|
346
|
+
insert_run_event(
|
|
347
|
+
conn,
|
|
348
|
+
run_id=run_id,
|
|
349
|
+
event_type=event_type,
|
|
350
|
+
payload={
|
|
351
|
+
"payer_user_id": decision.payer_user_id,
|
|
352
|
+
"funding_source": decision.funding_source.value,
|
|
353
|
+
"deny_reason": decision.deny_reason,
|
|
354
|
+
"profile_slug": profile_slug,
|
|
355
|
+
"provider": provider,
|
|
356
|
+
},
|
|
357
|
+
room_id=room_id,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def complete_run(
|
|
362
|
+
conn: sqlite3.Connection,
|
|
363
|
+
run_id: str,
|
|
364
|
+
*,
|
|
365
|
+
model: str = "",
|
|
366
|
+
provider: str = "",
|
|
367
|
+
funding_source: FundingSource,
|
|
368
|
+
payer_user_id: str,
|
|
369
|
+
receipt: Mapping[str, Any] | None = None,
|
|
370
|
+
) -> None:
|
|
371
|
+
update_run_status(conn, run_id, RunStatus.COMPLETED)
|
|
372
|
+
insert_run_event(conn, run_id=run_id, event_type="run.completed", payload=dict(receipt or {}))
|
|
373
|
+
insert_line_item(
|
|
374
|
+
conn,
|
|
375
|
+
run_id=run_id,
|
|
376
|
+
kind="llm_turn",
|
|
377
|
+
payer_user_id=payer_user_id,
|
|
378
|
+
funding_source=funding_source,
|
|
379
|
+
provider=provider or None,
|
|
380
|
+
model=model or None,
|
|
381
|
+
unit_label="turn",
|
|
382
|
+
units=1.0,
|
|
383
|
+
receipt={
|
|
384
|
+
"run_id": run_id,
|
|
385
|
+
"model": model,
|
|
386
|
+
"provider": provider,
|
|
387
|
+
"payer_user_id": payer_user_id,
|
|
388
|
+
"funding_source": funding_source.value,
|
|
389
|
+
**dict(receipt or {}),
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def fail_run(conn: sqlite3.Connection, run_id: str, *, reason: str = "") -> None:
|
|
395
|
+
update_run_status(conn, run_id, RunStatus.FAILED, deny_reason=reason or None)
|
|
396
|
+
insert_run_event(conn, run_id=run_id, event_type="run.failed", payload={"reason": reason})
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def list_run_events_for_room(room_id: str, *, limit: int = 40) -> list[dict[str, Any]]:
|
|
400
|
+
room_id = str(room_id or "").strip()
|
|
401
|
+
if not room_id:
|
|
402
|
+
return []
|
|
403
|
+
ensure_schema()
|
|
404
|
+
conn = _connect()
|
|
405
|
+
try:
|
|
406
|
+
rows = conn.execute(
|
|
407
|
+
"""
|
|
408
|
+
SELECT e.event_id, e.run_id, e.event_type, e.payload_json, e.created_at,
|
|
409
|
+
r.profile_slug, r.payer_user_id, r.funding_source, r.status AS run_status
|
|
410
|
+
FROM run_events e
|
|
411
|
+
JOIN runs r ON r.run_id = e.run_id
|
|
412
|
+
WHERE COALESCE(e.room_id, r.room_id) = ?
|
|
413
|
+
ORDER BY e.created_at DESC
|
|
414
|
+
LIMIT ?
|
|
415
|
+
""",
|
|
416
|
+
(room_id, max(1, limit)),
|
|
417
|
+
).fetchall()
|
|
418
|
+
finally:
|
|
419
|
+
conn.close()
|
|
420
|
+
out: list[dict[str, Any]] = []
|
|
421
|
+
for row in rows:
|
|
422
|
+
try:
|
|
423
|
+
payload = json.loads(str(row["payload_json"] or "{}"))
|
|
424
|
+
except json.JSONDecodeError:
|
|
425
|
+
payload = {}
|
|
426
|
+
out.append(
|
|
427
|
+
{
|
|
428
|
+
"id": f"run_event:{row['event_id']}",
|
|
429
|
+
"kind": "run_event",
|
|
430
|
+
"event_type": str(row["event_type"]),
|
|
431
|
+
"run_id": str(row["run_id"]),
|
|
432
|
+
"run_status": str(row["run_status"]),
|
|
433
|
+
"profile": str(row["profile_slug"] or ""),
|
|
434
|
+
"payer_user_id": str(row["payer_user_id"] or ""),
|
|
435
|
+
"funding_source": str(row["funding_source"] or ""),
|
|
436
|
+
"task_description": _event_description(str(row["event_type"]), payload),
|
|
437
|
+
"status": str(row["run_status"]),
|
|
438
|
+
"created_at": str(row["created_at"]),
|
|
439
|
+
"source": "run_ledger",
|
|
440
|
+
"payload": payload,
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
return out
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _event_description(event_type: str, payload: Mapping[str, Any]) -> str:
|
|
447
|
+
if event_type == "run.authorized":
|
|
448
|
+
fs = str(payload.get("funding_source") or "")
|
|
449
|
+
return f"Run authorized ({fs})" if fs else "Run authorized"
|
|
450
|
+
if event_type == "run.denied":
|
|
451
|
+
return f"Run denied: {payload.get('deny_reason') or 'policy'}"
|
|
452
|
+
if event_type == "run.completed":
|
|
453
|
+
return "Chat turn completed"
|
|
454
|
+
if event_type == "run.failed":
|
|
455
|
+
return f"Run failed: {payload.get('reason') or 'error'}"
|
|
456
|
+
if event_type == "slash.command":
|
|
457
|
+
cmd = str(payload.get("command") or "")
|
|
458
|
+
return f"Slash /{cmd}" if cmd else "Slash command"
|
|
459
|
+
if event_type == "cron.triggered":
|
|
460
|
+
return f"Cron: {payload.get('job') or payload.get('actor_id') or 'job'}"
|
|
461
|
+
if event_type == "kanban.task_created":
|
|
462
|
+
return f"Kanban: {payload.get('title') or 'task'}"
|
|
463
|
+
return event_type
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def get_run(conn: sqlite3.Connection, run_id: str) -> Run | None:
|
|
467
|
+
row = conn.execute("SELECT * FROM runs WHERE run_id = ?", (run_id,)).fetchone()
|
|
468
|
+
if not row:
|
|
469
|
+
return None
|
|
470
|
+
return _row_to_run(row)
|