traceforge-toolkit 0.1.0__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.
Files changed (205) hide show
  1. traceforge/__init__.py +72 -0
  2. traceforge/__main__.py +5 -0
  3. traceforge/_generated.py +254 -0
  4. traceforge/adapters/__init__.py +12 -0
  5. traceforge/adapters/base.py +82 -0
  6. traceforge/adapters/genai_otel.py +164 -0
  7. traceforge/adapters/mapped_json.py +297 -0
  8. traceforge/adapters/otel.py +220 -0
  9. traceforge/boundary/__init__.py +46 -0
  10. traceforge/boundary/data/boundary-model.joblib +0 -0
  11. traceforge/boundary/decode.py +87 -0
  12. traceforge/boundary/features.py +137 -0
  13. traceforge/boundary/inference.py +267 -0
  14. traceforge/boundary/inferencer.py +146 -0
  15. traceforge/classify/__init__.py +95 -0
  16. traceforge/classify/cmd.py +109 -0
  17. traceforge/classify/coding.py +213 -0
  18. traceforge/classify/config.py +554 -0
  19. traceforge/classify/core.py +266 -0
  20. traceforge/classify/data/binary_info.yaml +358 -0
  21. traceforge/classify/data/canonical_tools.yaml +251 -0
  22. traceforge/classify/data/effect_overrides.yaml +480 -0
  23. traceforge/classify/data/mcp_profiles.yaml +711 -0
  24. traceforge/classify/data/recommendation_rules.yaml +111 -0
  25. traceforge/classify/data/risk.yaml +534 -0
  26. traceforge/classify/data/shell_defaults.yaml +95 -0
  27. traceforge/classify/data/shell_rules.yaml +1192 -0
  28. traceforge/classify/data/tool_classifications.yaml +215 -0
  29. traceforge/classify/data/verb_inference.yaml +218 -0
  30. traceforge/classify/mcp.py +181 -0
  31. traceforge/classify/phases.py +75 -0
  32. traceforge/classify/powershell.py +93 -0
  33. traceforge/classify/registry.py +138 -0
  34. traceforge/classify/risk.py +553 -0
  35. traceforge/classify/rules.py +215 -0
  36. traceforge/classify/schema.yaml +282 -0
  37. traceforge/classify/shell.py +503 -0
  38. traceforge/classify/tools.py +91 -0
  39. traceforge/classify/workflow.py +32 -0
  40. traceforge/cli/__init__.py +42 -0
  41. traceforge/cli/config_cmd.py +130 -0
  42. traceforge/cli/detect.py +46 -0
  43. traceforge/cli/download_cmd.py +74 -0
  44. traceforge/cli/factory.py +60 -0
  45. traceforge/cli/gate_cmd.py +30 -0
  46. traceforge/cli/init_cmd.py +86 -0
  47. traceforge/cli/replay.py +130 -0
  48. traceforge/cli/runner.py +181 -0
  49. traceforge/cli/score.py +217 -0
  50. traceforge/cli/status.py +65 -0
  51. traceforge/cli/watch.py +297 -0
  52. traceforge/config/__init__.py +80 -0
  53. traceforge/config/defaults.py +149 -0
  54. traceforge/config/loader.py +239 -0
  55. traceforge/config/mappings.py +104 -0
  56. traceforge/config/models.py +579 -0
  57. traceforge/enricher.py +576 -0
  58. traceforge/formatting/__init__.py +12 -0
  59. traceforge/formatting/budget.py +92 -0
  60. traceforge/formatting/density.py +154 -0
  61. traceforge/gate/__init__.py +17 -0
  62. traceforge/gate/client.py +145 -0
  63. traceforge/gate/external.py +305 -0
  64. traceforge/gate/registry.py +108 -0
  65. traceforge/gate/server.py +174 -0
  66. traceforge/gates/__init__.py +8 -0
  67. traceforge/gates/pii.py +352 -0
  68. traceforge/gates/pii_patterns.yaml +228 -0
  69. traceforge/governance/__init__.py +121 -0
  70. traceforge/governance/assessor.py +441 -0
  71. traceforge/governance/budget.py +59 -0
  72. traceforge/governance/canonical.py +56 -0
  73. traceforge/governance/codec.py +414 -0
  74. traceforge/governance/context.py +249 -0
  75. traceforge/governance/drift.py +196 -0
  76. traceforge/governance/emitter.py +234 -0
  77. traceforge/governance/envelope.py +138 -0
  78. traceforge/governance/ifc.py +364 -0
  79. traceforge/governance/integrity.py +162 -0
  80. traceforge/governance/labeler.py +250 -0
  81. traceforge/governance/mcp_drift.py +328 -0
  82. traceforge/governance/monitor.py +539 -0
  83. traceforge/governance/observer.py +276 -0
  84. traceforge/governance/persistence.py +401 -0
  85. traceforge/governance/phase1.py +77 -0
  86. traceforge/governance/pii.py +276 -0
  87. traceforge/governance/pipeline.py +840 -0
  88. traceforge/governance/registry.py +110 -0
  89. traceforge/governance/results.py +183 -0
  90. traceforge/governance/risk_wrapper.py +113 -0
  91. traceforge/governance/rules.py +368 -0
  92. traceforge/governance/scorer.py +262 -0
  93. traceforge/governance/shield.py +318 -0
  94. traceforge/governance/state.py +466 -0
  95. traceforge/governance/types.py +176 -0
  96. traceforge/mappings/__init__.py +1 -0
  97. traceforge/mappings/aider.yaml +216 -0
  98. traceforge/mappings/aider_markdown.yaml +113 -0
  99. traceforge/mappings/amazonq.yaml +56 -0
  100. traceforge/mappings/antigravity.yaml +88 -0
  101. traceforge/mappings/claude.yaml +93 -0
  102. traceforge/mappings/cline.yaml +286 -0
  103. traceforge/mappings/codex.yaml +158 -0
  104. traceforge/mappings/continue_dev.yaml +57 -0
  105. traceforge/mappings/copilot.yaml +266 -0
  106. traceforge/mappings/copilot_markdown.yaml +72 -0
  107. traceforge/mappings/copilot_vscode.yaml +181 -0
  108. traceforge/mappings/crewai.yaml +592 -0
  109. traceforge/mappings/goose.yaml +128 -0
  110. traceforge/mappings/langgraph.yaml +165 -0
  111. traceforge/mappings/maf.yaml +90 -0
  112. traceforge/mappings/maf_transcript.yaml +99 -0
  113. traceforge/mappings/openai_agents.yaml +144 -0
  114. traceforge/mappings/opencode.yaml +473 -0
  115. traceforge/mappings/openhands.yaml +320 -0
  116. traceforge/mappings/pydantic_ai.yaml +183 -0
  117. traceforge/mappings/smolagents.yaml +89 -0
  118. traceforge/mappings/sweagent.yaml +82 -0
  119. traceforge/migrations/__init__.py +1 -0
  120. traceforge/migrations/env.py +60 -0
  121. traceforge/migrations/models.py +145 -0
  122. traceforge/migrations/runner.py +44 -0
  123. traceforge/migrations/script.py.mako +25 -0
  124. traceforge/migrations/versions/0001_initial.py +176 -0
  125. traceforge/migrations/versions/__init__.py +4 -0
  126. traceforge/models.py +29 -0
  127. traceforge/parsers/__init__.py +21 -0
  128. traceforge/parsers/aider.py +416 -0
  129. traceforge/parsers/base.py +226 -0
  130. traceforge/parsers/copilot.py +314 -0
  131. traceforge/phase/__init__.py +50 -0
  132. traceforge/phase/data/phase-model.joblib +0 -0
  133. traceforge/phase/data/potion-base-8M/README.md +99 -0
  134. traceforge/phase/data/potion-base-8M/config.json +13 -0
  135. traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
  136. traceforge/phase/data/potion-base-8M/modules.json +14 -0
  137. traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
  138. traceforge/phase/event_rows.py +107 -0
  139. traceforge/phase/features.py +468 -0
  140. traceforge/phase/inference.py +279 -0
  141. traceforge/phase/inferencer.py +171 -0
  142. traceforge/phase/segmentation.py +258 -0
  143. traceforge/pipeline.py +891 -0
  144. traceforge/preprocessors/__init__.py +34 -0
  145. traceforge/preprocessors/amazonq.py +224 -0
  146. traceforge/preprocessors/antigravity.py +116 -0
  147. traceforge/preprocessors/claude.py +95 -0
  148. traceforge/preprocessors/cline.py +36 -0
  149. traceforge/preprocessors/codex.py +311 -0
  150. traceforge/preprocessors/continue_dev.py +119 -0
  151. traceforge/preprocessors/copilot_vscode.py +171 -0
  152. traceforge/preprocessors/goose.py +156 -0
  153. traceforge/preprocessors/maf_transcript.py +84 -0
  154. traceforge/preprocessors/openai_agents.py +86 -0
  155. traceforge/preprocessors/opencode.py +85 -0
  156. traceforge/preprocessors/openhands.py +36 -0
  157. traceforge/preprocessors/pydantic_ai.py +62 -0
  158. traceforge/preprocessors/registry.py +24 -0
  159. traceforge/preprocessors/smolagents.py +90 -0
  160. traceforge/py.typed +0 -0
  161. traceforge/sdk/__init__.py +59 -0
  162. traceforge/sdk/gate_policy.py +63 -0
  163. traceforge/sdk/gate_types.py +140 -0
  164. traceforge/sdk/pipeline.py +265 -0
  165. traceforge/sdk/verdict.py +81 -0
  166. traceforge/sinks/__init__.py +23 -0
  167. traceforge/sinks/base.py +95 -0
  168. traceforge/sinks/callback.py +132 -0
  169. traceforge/sinks/console.py +112 -0
  170. traceforge/sinks/factory.py +94 -0
  171. traceforge/sinks/jsonl.py +125 -0
  172. traceforge/sinks/otel_exporter.py +212 -0
  173. traceforge/sinks/parquet.py +260 -0
  174. traceforge/sinks/s3.py +206 -0
  175. traceforge/sinks/sqlite_output.py +234 -0
  176. traceforge/sinks/webhook.py +136 -0
  177. traceforge/sources/__init__.py +18 -0
  178. traceforge/sources/auto_detect.py +173 -0
  179. traceforge/sources/base.py +45 -0
  180. traceforge/sources/file_poll.py +136 -0
  181. traceforge/sources/file_watch.py +221 -0
  182. traceforge/sources/http_poll.py +141 -0
  183. traceforge/sources/replay.py +63 -0
  184. traceforge/sources/sqlite.py +187 -0
  185. traceforge/sources/sse.py +198 -0
  186. traceforge/telemetry/__init__.py +192 -0
  187. traceforge/title/__init__.py +23 -0
  188. traceforge/title/_resolve.py +79 -0
  189. traceforge/title/context.py +295 -0
  190. traceforge/title/data/boilerplate_files.json +14 -0
  191. traceforge/title/heuristics.py +429 -0
  192. traceforge/title/hygiene.py +90 -0
  193. traceforge/title/inference.py +314 -0
  194. traceforge/title/inferencer.py +477 -0
  195. traceforge/title/naming.py +398 -0
  196. traceforge/trace.py +291 -0
  197. traceforge/tracking/__init__.py +30 -0
  198. traceforge/tracking/models.py +115 -0
  199. traceforge/tracking/phase_tracker.py +288 -0
  200. traceforge/types.py +315 -0
  201. traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
  202. traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
  203. traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
  204. traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
  205. traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,60 @@
1
+ """Alembic environment configuration for traceforge system storage.
2
+
3
+ This is used programmatically — not via the `alembic` CLI — so there is no
4
+ alembic.ini file. Instead, SystemStore calls run_migrations() directly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from alembic import context
10
+ from sqlalchemy import engine_from_config, pool
11
+
12
+ from traceforge.migrations.models import metadata
13
+
14
+
15
+ def run_migrations_offline() -> None:
16
+ """Run migrations in 'offline' mode (emit SQL without a live connection)."""
17
+ url = context.config.get_main_option("sqlalchemy.url")
18
+ context.configure(
19
+ url=url,
20
+ target_metadata=metadata,
21
+ literal_binds=True,
22
+ dialect_opts={"paramstyle": "qmark"},
23
+ )
24
+ with context.begin_transaction():
25
+ context.run_migrations()
26
+
27
+
28
+ def run_migrations_online() -> None:
29
+ """Run migrations in 'online' mode (with a live connection)."""
30
+ connectable = context.config.attributes.get("connection")
31
+
32
+ if connectable is not None:
33
+ # Connection already provided by the caller (SystemStore)
34
+ context.configure(connection=connectable, target_metadata=metadata)
35
+ with context.begin_transaction():
36
+ context.run_migrations()
37
+ else:
38
+ # Fallback: create engine from config
39
+ configuration = context.config.get_section(context.config.config_ini_section, {})
40
+ connectable = engine_from_config(
41
+ configuration,
42
+ prefix="sqlalchemy.",
43
+ poolclass=pool.NullPool,
44
+ )
45
+ with connectable.connect() as connection:
46
+ context.configure(connection=connection, target_metadata=metadata)
47
+ with context.begin_transaction():
48
+ context.run_migrations()
49
+
50
+
51
+ try:
52
+ if context.is_offline_mode():
53
+ run_migrations_offline()
54
+ else:
55
+ run_migrations_online()
56
+ except (AttributeError, NameError):
57
+ # Guard against accidental direct import outside Alembic context.
58
+ # When imported normally (not via Alembic's ScriptDirectory), the
59
+ # context proxy raises NameError (Alembic's ModuleClsProxy machinery).
60
+ pass
@@ -0,0 +1,145 @@
1
+ """SQLAlchemy table definitions for the traceforge system database.
2
+
3
+ These models serve as the migration target metadata. They are NOT used at
4
+ runtime for queries — the raw sqlite3 API remains for performance. Alembic
5
+ uses these to auto-generate diffs when new migrations are needed.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from sqlalchemy import Column, Float, Integer, MetaData, PrimaryKeyConstraint, Table, Text
11
+
12
+ metadata = MetaData()
13
+
14
+ session_state = Table(
15
+ "session_state",
16
+ metadata,
17
+ Column("session_id", Text, primary_key=True),
18
+ Column("total_tool_calls", Integer, nullable=False, server_default="0"),
19
+ Column("total_tokens", Integer, nullable=False, server_default="0"),
20
+ Column("elapsed_seconds", Float, nullable=False, server_default="0.0"),
21
+ Column("pressure", Integer, nullable=False, server_default="0"),
22
+ Column("phase_window_json", Text, nullable=False, server_default="[]"),
23
+ Column("last_assistant_json", Text),
24
+ Column("last_user_json", Text),
25
+ Column("event_count", Integer, nullable=False, server_default="0"),
26
+ Column("dropped_events", Integer, nullable=False, server_default="0"),
27
+ Column("last_sequence", Integer),
28
+ Column("last_event_id", Text),
29
+ Column("updated_at", Text, nullable=False, server_default=""),
30
+ )
31
+
32
+ processed_events = Table(
33
+ "processed_events",
34
+ metadata,
35
+ Column("source_event_key", Text, primary_key=True),
36
+ Column("session_id", Text, nullable=False),
37
+ Column("session_meta_json", Text),
38
+ Column("processed_at", Text, nullable=False),
39
+ )
40
+
41
+ drift_baselines = Table(
42
+ "drift_baselines",
43
+ metadata,
44
+ Column("agent_model", Text, nullable=False),
45
+ Column("repo", Text, nullable=False),
46
+ Column("phase_counts_json", Text, nullable=False),
47
+ Column("total_events", Integer, nullable=False),
48
+ Column("updated_at", Text, nullable=False),
49
+ PrimaryKeyConstraint("agent_model", "repo"),
50
+ )
51
+
52
+ content_hashes = Table(
53
+ "content_hashes",
54
+ metadata,
55
+ Column("repo", Text, nullable=False),
56
+ Column("file_path", Text, nullable=False),
57
+ Column("sha256", Text, nullable=False),
58
+ Column("updated_at", Text, nullable=False),
59
+ Column("updated_by_session", Text),
60
+ PrimaryKeyConstraint("repo", "file_path"),
61
+ )
62
+
63
+ session_summaries = Table(
64
+ "session_summaries",
65
+ metadata,
66
+ Column("session_id", Text, primary_key=True),
67
+ Column("repo", Text),
68
+ Column("agent_model", Text),
69
+ Column("started_at", Text, nullable=False),
70
+ Column("ended_at", Text),
71
+ Column("total_events", Integer),
72
+ Column("dropped_events", Integer, server_default="0"),
73
+ Column("budget_snapshot_json", Text),
74
+ Column("recommendation_counts_json", Text),
75
+ Column("drift_max", Float),
76
+ )
77
+
78
+ gate_endpoints = Table(
79
+ "gate_endpoints",
80
+ metadata,
81
+ Column("session_id", Text, primary_key=True),
82
+ Column("sock_path", Text, nullable=False),
83
+ Column("pid", Integer, nullable=False),
84
+ Column("registered_at", Text, nullable=False, server_default="(datetime('now'))"),
85
+ )
86
+
87
+ # ── Normalized 1NF tables ───────────────────────────────────────────────────
88
+ # Budget dimensions, the taint ledger, and MCP tool attributes are stored in
89
+ # first normal form from the outset — there is no JSON-blob/JSON-array shape to
90
+ # migrate away from. These are the single source of truth for their data.
91
+
92
+ # One row per (session, budget dimension, key). The atomic budget scalars
93
+ # (total_tool_calls, total_tokens, elapsed_seconds, pressure) are columns on
94
+ # session_state — they are already atomic and need no decomposition.
95
+ budget_counters = Table(
96
+ "budget_counters",
97
+ metadata,
98
+ Column("session_id", Text, nullable=False),
99
+ Column("dimension", Text, nullable=False),
100
+ Column("key", Text, nullable=False),
101
+ Column("count", Integer, nullable=False),
102
+ PrimaryKeyConstraint("session_id", "dimension", "key"),
103
+ )
104
+
105
+ # One row per taint-ledger entry. ``ordinal`` preserves append order so the
106
+ # bounded ring-buffer semantics survive a reload.
107
+ taint_entries = Table(
108
+ "taint_entries",
109
+ metadata,
110
+ Column("session_id", Text, nullable=False),
111
+ Column("ordinal", Integer, nullable=False),
112
+ Column("event_id", Text, nullable=False),
113
+ Column("source_event_key", Text, nullable=False),
114
+ Column("clearance", Text, nullable=False),
115
+ Column("source", Text, nullable=False),
116
+ Column("payload_pointer", Text, nullable=False),
117
+ PrimaryKeyConstraint("session_id", "ordinal"),
118
+ )
119
+
120
+ # Scalar MCP tool fingerprint. The many-valued role/capability/scope attributes
121
+ # live in mcp_profile_attributes below (1NF), one row per value.
122
+ mcp_profiles = Table(
123
+ "mcp_profiles",
124
+ metadata,
125
+ Column("server", Text, nullable=False),
126
+ Column("tool_name", Text, nullable=False),
127
+ Column("description_hash", Text, nullable=False),
128
+ Column("schema_hash", Text, nullable=False),
129
+ Column("registered_effect", Text),
130
+ Column("clearance", Text),
131
+ Column("first_seen", Text, nullable=False),
132
+ Column("last_seen", Text, nullable=False),
133
+ PrimaryKeyConstraint("server", "tool_name"),
134
+ )
135
+
136
+ # One row per many-valued MCP attribute (role / capability / scope).
137
+ mcp_profile_attributes = Table(
138
+ "mcp_profile_attributes",
139
+ metadata,
140
+ Column("server", Text, nullable=False),
141
+ Column("tool_name", Text, nullable=False),
142
+ Column("attr_type", Text, nullable=False), # 'role' | 'capability' | 'scope'
143
+ Column("attr_value", Text, nullable=False),
144
+ PrimaryKeyConstraint("server", "tool_name", "attr_type", "attr_value"),
145
+ )
@@ -0,0 +1,44 @@
1
+ """Programmatic Alembic migration runner for traceforge system storage.
2
+
3
+ Called by SystemStore on initialization to bring the database to HEAD.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from pathlib import Path
10
+
11
+ from alembic import command
12
+ from alembic.config import Config
13
+ from sqlalchemy import Connection
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _MIGRATIONS_DIR = Path(__file__).resolve().parent
18
+
19
+
20
+ def _make_config(connection: Connection) -> Config:
21
+ """Build an Alembic Config that uses the provided connection."""
22
+ cfg = Config()
23
+ cfg.set_main_option("script_location", str(_MIGRATIONS_DIR))
24
+ cfg.attributes["connection"] = connection
25
+ return cfg
26
+
27
+
28
+ def run_migrations(connection: Connection) -> None:
29
+ """Run all pending migrations against the given connection.
30
+
31
+ This is called once per SystemStore initialization. It is idempotent —
32
+ if the database is already at HEAD, this is a no-op.
33
+ """
34
+ cfg = _make_config(connection)
35
+ command.upgrade(cfg, "head")
36
+ logger.debug("Migrations applied successfully")
37
+
38
+
39
+ def current_revision(connection: Connection) -> str | None:
40
+ """Return the current alembic revision (or None if unversioned)."""
41
+ from alembic.runtime.migration import MigrationContext
42
+
43
+ ctx = MigrationContext.configure(connection)
44
+ return ctx.get_current_revision()
@@ -0,0 +1,25 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+ ${imports if imports else ""}
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = ${repr(up_revision)}
15
+ down_revision: str | None = ${repr(down_revision)}
16
+ branch_labels: tuple[str, ...] | None = ${repr(branch_labels)}
17
+ depends_on: tuple[str, ...] | None = ${repr(depends_on)}
18
+
19
+
20
+ def upgrade() -> None:
21
+ ${upgrades if upgrades else "pass"}
22
+
23
+
24
+ def downgrade() -> None:
25
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,176 @@
1
+ """Initial schema — the single, normalized system database.
2
+
3
+ Budget dimensions, the taint ledger, and MCP tool profiles are stored in
4
+ first-normal-form tables from the outset (``budget_counters``,
5
+ ``taint_entries``, ``mcp_profiles`` + ``mcp_profile_attributes``); the atomic
6
+ budget scalars are plain columns on ``session_state``. No JSON-blob or
7
+ JSON-array representation of these ever exists.
8
+
9
+ Revision ID: 0001_initial
10
+ Revises: None
11
+ Create Date: 2026-06-15
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from alembic import op
17
+
18
+ revision: str = "0001_initial"
19
+ down_revision: str | None = None
20
+ branch_labels: tuple[str, ...] | None = None
21
+ depends_on: tuple[str, ...] | None = None
22
+
23
+
24
+ def upgrade() -> None:
25
+ # PRAGMAs are set by SystemStore.__init__ before migrations run.
26
+ # They are non-transactional and must not appear inside a migration
27
+ # (they execute outside rollback scope and would confuse partial-failure recovery).
28
+
29
+ # Use IF NOT EXISTS to handle concurrent first-run race: two processes
30
+ # creating a fresh system.db simultaneously won't crash each other.
31
+ # We use exec_driver_sql() to bypass SQLAlchemy's text() parameter parsing,
32
+ # which misinterprets colons in JSON default values as bind parameters.
33
+ conn = op.get_bind()
34
+
35
+ conn.exec_driver_sql("""
36
+ CREATE TABLE IF NOT EXISTS session_state (
37
+ session_id TEXT PRIMARY KEY,
38
+ total_tool_calls INTEGER NOT NULL DEFAULT 0,
39
+ total_tokens INTEGER NOT NULL DEFAULT 0,
40
+ elapsed_seconds REAL NOT NULL DEFAULT 0.0,
41
+ pressure INTEGER NOT NULL DEFAULT 0,
42
+ phase_window_json TEXT NOT NULL DEFAULT '[]',
43
+ last_assistant_json TEXT,
44
+ last_user_json TEXT,
45
+ event_count INTEGER NOT NULL DEFAULT 0,
46
+ dropped_events INTEGER NOT NULL DEFAULT 0,
47
+ last_sequence INTEGER,
48
+ last_event_id TEXT,
49
+ updated_at TEXT NOT NULL DEFAULT ''
50
+ )
51
+ """)
52
+
53
+ conn.exec_driver_sql("""
54
+ CREATE TABLE IF NOT EXISTS processed_events (
55
+ source_event_key TEXT PRIMARY KEY,
56
+ session_id TEXT NOT NULL,
57
+ session_meta_json TEXT,
58
+ processed_at TEXT NOT NULL
59
+ )
60
+ """)
61
+
62
+ # ── Normalized MCP tool profiles ──
63
+ # Scalar fingerprint per (server, tool). The many-valued role/capability/
64
+ # scope attributes live in mcp_profile_attributes (1NF), one row per value.
65
+ conn.exec_driver_sql("""
66
+ CREATE TABLE IF NOT EXISTS mcp_profiles (
67
+ server TEXT NOT NULL,
68
+ tool_name TEXT NOT NULL,
69
+ description_hash TEXT NOT NULL,
70
+ schema_hash TEXT NOT NULL,
71
+ registered_effect TEXT,
72
+ clearance TEXT,
73
+ first_seen TEXT NOT NULL,
74
+ last_seen TEXT NOT NULL,
75
+ PRIMARY KEY (server, tool_name)
76
+ )
77
+ """)
78
+
79
+ conn.exec_driver_sql("""
80
+ CREATE TABLE IF NOT EXISTS mcp_profile_attributes (
81
+ server TEXT NOT NULL,
82
+ tool_name TEXT NOT NULL,
83
+ attr_type TEXT NOT NULL,
84
+ attr_value TEXT NOT NULL,
85
+ PRIMARY KEY (server, tool_name, attr_type, attr_value)
86
+ )
87
+ """)
88
+
89
+ # ── Normalized budget counters ──
90
+ # One row per (session, dimension, key). Replaces the by_* maps that a JSON
91
+ # budget blob would otherwise carry; the atomic scalars are columns on
92
+ # session_state.
93
+ conn.exec_driver_sql("""
94
+ CREATE TABLE IF NOT EXISTS budget_counters (
95
+ session_id TEXT NOT NULL,
96
+ dimension TEXT NOT NULL,
97
+ key TEXT NOT NULL,
98
+ count INTEGER NOT NULL,
99
+ PRIMARY KEY (session_id, dimension, key)
100
+ )
101
+ """)
102
+
103
+ # ── Normalized taint ledger ──
104
+ # One row per taint entry; ordinal preserves append order so the bounded
105
+ # ring-buffer semantics survive a reload.
106
+ conn.exec_driver_sql("""
107
+ CREATE TABLE IF NOT EXISTS taint_entries (
108
+ session_id TEXT NOT NULL,
109
+ ordinal INTEGER NOT NULL,
110
+ event_id TEXT NOT NULL,
111
+ source_event_key TEXT NOT NULL,
112
+ clearance TEXT NOT NULL,
113
+ source TEXT NOT NULL,
114
+ payload_pointer TEXT NOT NULL,
115
+ PRIMARY KEY (session_id, ordinal)
116
+ )
117
+ """)
118
+
119
+ conn.exec_driver_sql("""
120
+ CREATE TABLE IF NOT EXISTS drift_baselines (
121
+ agent_model TEXT NOT NULL,
122
+ repo TEXT NOT NULL,
123
+ phase_counts_json TEXT NOT NULL,
124
+ total_events INTEGER NOT NULL,
125
+ updated_at TEXT NOT NULL,
126
+ PRIMARY KEY (agent_model, repo)
127
+ )
128
+ """)
129
+
130
+ conn.exec_driver_sql("""
131
+ CREATE TABLE IF NOT EXISTS content_hashes (
132
+ repo TEXT NOT NULL,
133
+ file_path TEXT NOT NULL,
134
+ sha256 TEXT NOT NULL,
135
+ updated_at TEXT NOT NULL,
136
+ updated_by_session TEXT,
137
+ PRIMARY KEY (repo, file_path)
138
+ )
139
+ """)
140
+
141
+ conn.exec_driver_sql("""
142
+ CREATE TABLE IF NOT EXISTS session_summaries (
143
+ session_id TEXT PRIMARY KEY,
144
+ repo TEXT,
145
+ agent_model TEXT,
146
+ started_at TEXT NOT NULL,
147
+ ended_at TEXT,
148
+ total_events INTEGER,
149
+ dropped_events INTEGER DEFAULT 0,
150
+ budget_snapshot_json TEXT,
151
+ recommendation_counts_json TEXT,
152
+ drift_max REAL
153
+ )
154
+ """)
155
+
156
+ conn.exec_driver_sql("""
157
+ CREATE TABLE IF NOT EXISTS gate_endpoints (
158
+ session_id TEXT PRIMARY KEY,
159
+ sock_path TEXT NOT NULL,
160
+ pid INTEGER NOT NULL,
161
+ registered_at TEXT NOT NULL DEFAULT (datetime('now'))
162
+ )
163
+ """)
164
+
165
+
166
+ def downgrade() -> None:
167
+ op.drop_table("gate_endpoints")
168
+ op.drop_table("session_summaries")
169
+ op.drop_table("content_hashes")
170
+ op.drop_table("drift_baselines")
171
+ op.drop_table("taint_entries")
172
+ op.drop_table("budget_counters")
173
+ op.drop_table("mcp_profile_attributes")
174
+ op.drop_table("mcp_profiles")
175
+ op.drop_table("processed_events")
176
+ op.drop_table("session_state")
@@ -0,0 +1,4 @@
1
+ """Alembic migration versions for traceforge system storage."""
2
+
3
+ # This must be updated whenever a new migration is added.
4
+ LATEST_REVISION = "0001_initial"
traceforge/models.py ADDED
@@ -0,0 +1,29 @@
1
+ """Base Pydantic model classes for all traceforge models.
2
+
3
+ Defines two base classes that encode structural policies:
4
+
5
+ - ``StrictModel``: Rejects unknown fields (``extra="forbid"``).
6
+ Used for all configuration and mapping models where typos must be caught.
7
+
8
+ - ``FrozenModel``: Immutable after construction (``frozen=True``).
9
+ Used for domain value objects that flow through the pipeline.
10
+
11
+ All traceforge models inherit from one of these — never from raw ``BaseModel``.
12
+ This eliminates per-class ``model_config`` repetition and enforces consistency.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pydantic import BaseModel, ConfigDict
18
+
19
+
20
+ class StrictModel(BaseModel):
21
+ """Base for configuration / schema models. Rejects unknown fields."""
22
+
23
+ model_config = ConfigDict(extra="forbid")
24
+
25
+
26
+ class FrozenModel(BaseModel):
27
+ """Base for immutable domain objects. Cannot be mutated after construction."""
28
+
29
+ model_config = ConfigDict(frozen=True)
@@ -0,0 +1,21 @@
1
+ """Pre-parsers that convert non-JSONL agent logs into structured event dicts."""
2
+
3
+ from traceforge.parsers.aider import AiderPreParser
4
+ from traceforge.parsers.base import (
5
+ Block,
6
+ MarkdownPreParser,
7
+ node_text,
8
+ strip_blockquote_markers,
9
+ try_parse_json,
10
+ )
11
+ from traceforge.parsers.copilot import CopilotPreParser
12
+
13
+ __all__ = [
14
+ "AiderPreParser",
15
+ "Block",
16
+ "CopilotPreParser",
17
+ "MarkdownPreParser",
18
+ "node_text",
19
+ "strip_blockquote_markers",
20
+ "try_parse_json",
21
+ ]