mikeboone Claude Fable 5.1 commited on
Commit
877abd8
·
1 Parent(s): 0bcf580

feat(reporting): production runs reach Snowflake DEMO_RUN/DEMO_RESULTS the moment a build completes

Browse files

The DemoPrep reporting liveboard reads DEMOPREP_REPORTING.REPORTING (DEMO_RUN,
DEMO_RESULTS, V_DEMO_RUN), which until now was filled only by the hand-run loader
dev_notes/reporting_load_runs.py.txt. Nobody ran it Oct 22-26 and five days of SE
builds were missing. A production run - a real Salesforce opportunity id (15/18
alphanumerics, never a TEST-/REPLAY-/XXTEST marker) with environment prod - is now
written the moment its build completes; every other run stays Supabase-only for the
loader, as before.

- demoprep_app/reporting/writer.py: the loader's single-run logic, importable. Same
rules verbatim (SFID, TEST_MARKER, INTERNAL, owner skips, IS_TEST_RUN sticky,
COALESCE on the opportunity id, err_bucket/cluster/space_of, derived model URL),
LOAD_LEDGER keyed by SOURCE_INTAKE_ID so the loader updates instead of duplicating,
one transaction, fully-qualified statements, never raises.
- supabase_client: fetch_mcp_intake_row (the intake row with its id + created_at);
mcp_result_record extracted from log_mcp_result so the stored row and the
write-through share one shaping.
- mcp_server: _start_reporting_write_through, called in _run_build.result() right
after log_mcp_result, daemon thread, try/except everywhere; no tool signature changed.
- tests/test_reporting_writer.py (48): the rule, the rows, the statements against a
fake cursor, the orchestration, the hook. SQL proven against real Snowflake in a
rolled-back transaction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

demoprep_app/reporting/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ """Reporting: the Snowflake tables behind the DemoPrep reporting liveboard
2
+ (DEMOPREP_REPORTING.REPORTING — DEMO_RUN, DEMO_RESULTS, LOAD_LEDGER, V_DEMO_RUN).
3
+
4
+ `writer` puts ONE completed production build there the moment it finishes. The
5
+ hand-run loader (dev_notes/reporting_load_runs.py.txt) remains the backfill for
6
+ everything else and finds the writer's rows through LOAD_LEDGER, so it updates
7
+ them instead of inserting them a second time.
8
+ """
demoprep_app/reporting/writer.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Write ONE completed production build into the Snowflake reporting tables now.
2
+
3
+ Until 2026-10-26 the tables the DemoPrep reporting liveboard reads
4
+ (DEMOPREP_REPORTING.REPORTING.DEMO_RUN / DEMO_RESULTS, view V_DEMO_RUN) were filled
5
+ only by the hand-run loader, dev_notes/reporting_load_runs.py.txt, from the build
6
+ log in Supabase (demo_history rows mcp_intake + mcp_result). Nobody ran it for five
7
+ days and five days of SE builds were missing. This module is the loader's
8
+ single-run logic, importable, so the MCP server can write a PRODUCTION run the
9
+ moment its build completes (mcp_server._run_build -> _start_reporting_write_through).
10
+
11
+ A production run (boone's rule) is a request whose salesforce_opportunity_id is a
12
+ real Salesforce id — 15 or 18 alphanumerics; the test markers TEST-, REPLAY- and
13
+ XXTEST never qualify — AND whose environment is prod ("prod" / "production";
14
+ "staging" / "stage" do not qualify). Every other run keeps the old behaviour:
15
+ Supabase only, picked up by the loader later.
16
+
17
+ Everything else is the loader's rule, kept verbatim so the two writers agree:
18
+ * SFID / TEST_MARKER, the INTERNAL company set, the testrunner / no.such.person
19
+ owner skip and the IGNORE company skip (`skip_reason`);
20
+ * IS_TEST_RUN = the owner is mike.boone@thoughtspot.com, and sticky in the MERGE;
21
+ * SALESFORCE_OPPORTUNITY_ID is COALESCEd on update, so a human-entered id is
22
+ never overwritten with NULL;
23
+ * `err_bucket`, `cluster`, `space_of`, and the model URL derived from ts_url +
24
+ model_guid when the result carries none;
25
+ * LOAD_LEDGER keyed by SOURCE_INTAKE_ID = the demo_history intake row id. The
26
+ loader finds that entry on its next run and UPDATEs the same DEMO_RUN_KEY, so
27
+ a write-through row is never counted twice.
28
+
29
+ Not done here, left to the loader's next run: inheriting an opportunity id from a
30
+ prospect's other runs (moot — a write-through run always has one) and syncing
31
+ app-emitted findings into DEMO_FINDINGS.
32
+
33
+ The loader stages rows in _RUN_STG / _RES_STG temp tables and MERGEs from them;
34
+ with exactly one run the same statements bind the row's values directly
35
+ (`USING (SELECT %s AS ...)`), fully qualified, inside one transaction.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import re
40
+ from datetime import datetime, timezone
41
+ from typing import Any, Callable, Dict, Optional
42
+
43
+ REPORTING_SCHEMA = "DEMOPREP_REPORTING.REPORTING"
44
+
45
+ # A Salesforce opportunity id is 15 or 18 alphanumerics. Anything else (harness
46
+ # sentinels like 'REPLAY-NOT-A-DEAL-…', 'NONE', free text) is NOT an id.
47
+ SFID = re.compile(r"^[A-Za-z0-9]{15}(?:[A-Za-z0-9]{3})?$")
48
+ # Test builds send a marker in place of an opportunity id (tests/*:
49
+ # TEST_OPPORTUNITY_ID = "TEST-NOT-A-DEAL"; older replays "REPLAY-NOT-A-DEAL-<CO>";
50
+ # XXTEST accepted too). A marked request is a test, never a demo for a deal.
51
+ TEST_MARKER = re.compile(r"^(TEST-|REPLAY-|XXTEST)", re.I)
52
+ # Fictional prospects the harnesses build; never reported.
53
+ INTERNAL = {"vantage capital", "northwind retail", "atlas freight", "cloudscape",
54
+ "streamly", "meridian health", "meridian health system"}
55
+ # Whose runs are flagged IS_TEST_RUN (excluded from SE figures).
56
+ TEST_RUN_OWNER = "mike.boone@thoughtspot.com"
57
+
58
+ _ENVIRONMENTS = {"prod": "prod", "production": "prod", "staging": "staging", "stage": "staging"}
59
+
60
+
61
+ # --- the rule ---------------------------------------------------------------- #
62
+ def normalize_environment(value: Any) -> str:
63
+ """'prod' | 'staging' | '' — the same synonyms build_demo_from_brief accepts."""
64
+ return _ENVIRONMENTS.get(str(value or "").strip().lower(), "")
65
+
66
+
67
+ def opportunity_id(value: Any) -> Optional[str]:
68
+ """The id if it is a well-formed Salesforce id, else None (the loader's opp_id)."""
69
+ v = str(value or "").strip()
70
+ return v if v and SFID.match(v) else None
71
+
72
+
73
+ def is_production_run(opportunity: Any, environment: Any) -> bool:
74
+ """boone's rule: a real Salesforce opportunity id AND environment prod."""
75
+ v = str(opportunity or "").strip()
76
+ if not v or TEST_MARKER.match(v) or not SFID.match(v):
77
+ return False
78
+ return normalize_environment(environment) == "prod"
79
+
80
+
81
+ def skip_reason(owner_email: Any, company_name: Any) -> Optional[str]:
82
+ """Why the loader would skip this request outright, or None. Verbatim from the
83
+ loader's per-request filter (no.such.person = the harness's unprovisioned-user
84
+ probe; IGNORE = a request marked to be left out; INTERNAL = fictional prospects)."""
85
+ o = str(owner_email or "").lower()
86
+ co = str(company_name or "").strip()
87
+ if not o:
88
+ return "no owner"
89
+ if "testrunner" in o:
90
+ return "testrunner owner"
91
+ if o.startswith("no.such.person"):
92
+ return "no.such.person owner"
93
+ if "IGNORE" in co:
94
+ return "IGNORE company"
95
+ if co.lower() in INTERNAL:
96
+ return "internal company"
97
+ return None
98
+
99
+
100
+ # --- the loader's helpers, verbatim -------------------------------------------- #
101
+ def err_bucket(t: Any) -> Optional[str]:
102
+ if not t:
103
+ return None
104
+ l = str(t).lower()
105
+ for k, v in (("timed out", "llm_timeout"), ("schema deployment", "schema_deploy"),
106
+ ("quality gate", "data_quality_gate"), ("concrete values", "blueprint_cardinality"),
107
+ ("placeholder", "blueprint_placeholder_names"),
108
+ ("failed validation after repair", "blueprint_validation_other"),
109
+ ("liveboard creation", "liveboard_creation"),
110
+ ("region not supported", "geo_region_unsupported"), ("tml export", "tml_export"),
111
+ ("authentication failed", "thoughtspot_auth"), ("compilation error", "sql_compilation"),
112
+ ("permission denied", "filesystem_permission"), ("max_tokens", "blueprint_truncated"),
113
+ ("hf_token", "portal_token_missing")):
114
+ if k in l:
115
+ return v
116
+ return "other"
117
+
118
+
119
+ def cluster(u: Any) -> Optional[str]:
120
+ u = (u or "").lower()
121
+ return ("sebe" if "sebe" in u else
122
+ ("se-thoughtspot-cloud" if "se-thoughtspot" in u else
123
+ ("techpartners" if "techpartners" in u else None)))
124
+
125
+
126
+ TESTKEYS = {"with_portal", "use_case", "resolved_ts_url"}
127
+
128
+
129
+ def space_of(irf: Dict[str, Any]) -> str:
130
+ """prod|test from the Space's own stamp when present (since the Sep 22 release
131
+ both Spaces stamp space_id); the key fingerprint is only trusted for older,
132
+ unstamped rows."""
133
+ sid = (irf.get("space_id") or "").split("/")[-1]
134
+ if sid == "test-mcp":
135
+ return "test"
136
+ if sid == "mcp":
137
+ return "prod"
138
+ return "test" if (TESTKEYS & set(irf.keys())) else "prod"
139
+
140
+
141
+ # --- the rows ------------------------------------------------------------------- #
142
+ def shape_rows(intake_row: Dict[str, Any], result_record: Dict[str, Any],
143
+ completed_at: Optional[str]) -> tuple[Dict[str, Any], Dict[str, Any]]:
144
+ """The loader's _RUN_STG and _RES_STG rows for one request, keyed by the staging
145
+ column names.
146
+
147
+ intake_row — the demo_history mcp_intake row as stored: id, created_at,
148
+ company_name, use_case, results (the full call payload).
149
+ result_record — the `results` JSON of the matching mcp_result row
150
+ (supabase_client.mcp_result_record), or {} when the build left none.
151
+ completed_at — the result row's created_at (ISO), or None.
152
+ """
153
+ irf = intake_row.get("results") or {}
154
+ rr = result_record or {}
155
+ rid = irf.get("run_id") or ""
156
+ o = (irf.get("owner_email") or "").lower()
157
+ co = (intake_row.get("company_name") or "").strip()
158
+ errs = rr.get("errors") or []
159
+ ts = (irf.get("ts_url") or rr.get("ts_url") or "").rstrip("/") or None
160
+ mg = rr.get("model_guid") or None
161
+ run = {
162
+ "INTAKE_ID": str(intake_row["id"]),
163
+ "RUN_ID": rid,
164
+ "SFDC": opportunity_id(irf.get("salesforce_opportunity_id")
165
+ or rr.get("salesforce_opportunity_id")),
166
+ "EMAIL": o,
167
+ "PROSPECT": co,
168
+ }
169
+ res = {
170
+ "INTAKE_ID": str(intake_row["id"]),
171
+ "USE_CASE": intake_row.get("use_case") or None,
172
+ "REQUESTED_AT": intake_row["created_at"],
173
+ "COMPLETED_AT": completed_at if rr else None,
174
+ "ELAPSED": rr.get("elapsed_seconds"),
175
+ "STATUS": rr.get("build_status") or ("no_result" if not rr else "unknown"),
176
+ "ERR_CAT": err_bucket(errs[0] if errs else None),
177
+ "SPACE": space_of(irf),
178
+ "CLUSTER_NAME": cluster(ts),
179
+ "IS_TEST": o == TEST_RUN_OWNER,
180
+ "LB_GUID": rr.get("liveboard_guid") or None,
181
+ "LB_URL": rr.get("liveboard_url") or None,
182
+ "MODEL_GUID": mg,
183
+ "MODEL_URL": (rr.get("model_url")
184
+ or (f"{ts}/#/data/tables/{mg}" if ts and mg else None)) or None,
185
+ "PORTAL_URL": (rr.get("portal_url") or "").strip() or None,
186
+ "SCHEMA_NAME": rr.get("schema") or None,
187
+ "ERR_TXT": (str(errs[0])[:4000] if errs else None),
188
+ }
189
+ return run, res
190
+
191
+
192
+ # --- the statements --------------------------------------------------------------- #
193
+ _S = REPORTING_SCHEMA
194
+
195
+ SQL_LEDGER_LOOKUP = f"SELECT DEMO_RUN_KEY FROM {_S}.LOAD_LEDGER WHERE SOURCE_INTAKE_ID=%s"
196
+
197
+ # The loader's WHEN MATCHED branch for DEMO_RUN: a human-entered opportunity id is
198
+ # never overwritten with NULL.
199
+ SQL_RUN_UPDATE = f"""UPDATE {_S}.DEMO_RUN SET RUN_ID=%s,
200
+ SALESFORCE_OPPORTUNITY_ID=COALESCE(SALESFORCE_OPPORTUNITY_ID,%s), RUNNER_EMAIL=%s, PROSPECT_NAME=%s
201
+ WHERE DEMO_RUN_KEY=%s"""
202
+
203
+ SQL_RUN_INSERT = (f"INSERT INTO {_S}.DEMO_RUN (RUN_ID, SALESFORCE_OPPORTUNITY_ID, RUNNER_EMAIL, PROSPECT_NAME) "
204
+ "VALUES (%s,%s,%s,%s)")
205
+
206
+ # The key the insert was given: the loader finds it the same way, by the row's
207
+ # identity among rows the ledger does not know yet.
208
+ SQL_RUN_KEY_OF_INSERT = f"""SELECT MAX(r.DEMO_RUN_KEY) FROM {_S}.DEMO_RUN r
209
+ WHERE r.RUN_ID=%s AND r.RUNNER_EMAIL=%s AND r.PROSPECT_NAME=%s
210
+ AND NOT EXISTS (SELECT 1 FROM {_S}.LOAD_LEDGER l WHERE l.DEMO_RUN_KEY=r.DEMO_RUN_KEY)"""
211
+
212
+ SQL_LEDGER_INSERT = f"INSERT INTO {_S}.LOAD_LEDGER (SOURCE_INTAKE_ID, DEMO_RUN_KEY) VALUES (%s,%s)"
213
+
214
+ # The loader's DEMO_RESULTS MERGE, with the staged row bound in place of _RES_STG.
215
+ # IS_TEST_RUN is sticky: a manual test flag is never un-set by a reload.
216
+ SQL_RESULTS_MERGE = f"""MERGE INTO {_S}.DEMO_RESULTS t USING (
217
+ SELECT %s AS DEMO_RUN_KEY, %s AS USE_CASE, TO_TIMESTAMP_TZ(%s) AS REQUESTED_AT, TO_TIMESTAMP_TZ(%s) AS COMPLETED_AT,
218
+ %s AS ELAPSED, %s AS STATUS, %s AS ERR_CAT, %s AS SPACE, %s AS CLUSTER_NAME, %s AS IS_TEST,
219
+ %s AS LB_GUID, %s AS LB_URL, %s AS MODEL_GUID, %s AS MODEL_URL, %s AS PORTAL_URL, %s AS SCHEMA_NAME, %s AS ERR_TXT
220
+ ) s ON t.DEMO_RUN_KEY=s.DEMO_RUN_KEY
221
+ WHEN MATCHED THEN UPDATE SET USE_CASE=s.USE_CASE, REQUESTED_AT=s.REQUESTED_AT, COMPLETED_AT=s.COMPLETED_AT, ELAPSED_SECONDS=s.ELAPSED, STATUS=s.STATUS,
222
+ ERROR_CATEGORY=s.ERR_CAT, SPACE=s.SPACE, CLUSTER_NAME=COALESCE(s.CLUSTER_NAME,t.CLUSTER_NAME), IS_TEST_RUN=(t.IS_TEST_RUN OR s.IS_TEST),
223
+ SOURCE_LOADED_AT=CURRENT_TIMESTAMP(),
224
+ LIVEBOARD_GUID=COALESCE(s.LB_GUID,t.LIVEBOARD_GUID), LIVEBOARD_URL=COALESCE(s.LB_URL,t.LIVEBOARD_URL), MODEL_GUID=COALESCE(s.MODEL_GUID,t.MODEL_GUID),
225
+ MODEL_URL=COALESCE(s.MODEL_URL,t.MODEL_URL), PORTAL_URL=COALESCE(s.PORTAL_URL,t.PORTAL_URL), SNOWFLAKE_SCHEMA=COALESCE(s.SCHEMA_NAME,t.SNOWFLAKE_SCHEMA), ERROR_TEXT=s.ERR_TXT
226
+ WHEN NOT MATCHED THEN INSERT (DEMO_RUN_KEY,USE_CASE,REQUESTED_AT,COMPLETED_AT,ELAPSED_SECONDS,STATUS,ERROR_CATEGORY,SPACE,CLUSTER_NAME,IS_TEST_RUN,SOURCE_LOADED_AT,
227
+ LIVEBOARD_GUID,LIVEBOARD_URL,MODEL_GUID,MODEL_URL,PORTAL_URL,SNOWFLAKE_SCHEMA,ERROR_TEXT)
228
+ VALUES (s.DEMO_RUN_KEY,s.USE_CASE,s.REQUESTED_AT,s.COMPLETED_AT,s.ELAPSED,s.STATUS,s.ERR_CAT,s.SPACE,s.CLUSTER_NAME,s.IS_TEST,CURRENT_TIMESTAMP(),
229
+ s.LB_GUID,s.LB_URL,s.MODEL_GUID,s.MODEL_URL,s.PORTAL_URL,s.SCHEMA_NAME,s.ERR_TXT)"""
230
+
231
+ _RES_ORDER = ("USE_CASE", "REQUESTED_AT", "COMPLETED_AT", "ELAPSED", "STATUS", "ERR_CAT", "SPACE",
232
+ "CLUSTER_NAME", "IS_TEST", "LB_GUID", "LB_URL", "MODEL_GUID", "MODEL_URL", "PORTAL_URL",
233
+ "SCHEMA_NAME", "ERR_TXT")
234
+
235
+
236
+ def write_run(cur, run: Dict[str, Any], res: Dict[str, Any]) -> Dict[str, Any]:
237
+ """MERGE one request into DEMO_RUN / LOAD_LEDGER / DEMO_RESULTS through `cur`
238
+ (a Snowflake cursor; the caller owns the transaction). Returns
239
+ {"demo_run_key": <key>, "action": "inserted" | "updated"}."""
240
+ cur.execute(SQL_LEDGER_LOOKUP, (run["INTAKE_ID"],))
241
+ hit = cur.fetchone()
242
+ if hit and hit[0] is not None:
243
+ key = int(hit[0])
244
+ cur.execute(SQL_RUN_UPDATE, (run["RUN_ID"], run["SFDC"], run["EMAIL"], run["PROSPECT"], key))
245
+ action = "updated"
246
+ else:
247
+ cur.execute(SQL_RUN_INSERT, (run["RUN_ID"], run["SFDC"], run["EMAIL"], run["PROSPECT"]))
248
+ cur.execute(SQL_RUN_KEY_OF_INSERT, (run["RUN_ID"], run["EMAIL"], run["PROSPECT"]))
249
+ got = cur.fetchone()
250
+ if not got or got[0] is None:
251
+ raise RuntimeError("DEMO_RUN insert left no un-ledgered row to key")
252
+ key = int(got[0])
253
+ cur.execute(SQL_LEDGER_INSERT, (run["INTAKE_ID"], key))
254
+ action = "inserted"
255
+ cur.execute(SQL_RESULTS_MERGE, (key, *[res[k] for k in _RES_ORDER]))
256
+ return {"demo_run_key": key, "action": action}
257
+
258
+
259
+ # --- the entry point the server calls ------------------------------------------------ #
260
+ def _default_connect():
261
+ from snowflake_auth import get_snowflake_connection
262
+ return get_snowflake_connection()
263
+
264
+
265
+ def _default_fetch_intake(run_id: str) -> Optional[Dict[str, Any]]:
266
+ from supabase_client import fetch_mcp_intake_row
267
+ return fetch_mcp_intake_row(run_id)
268
+
269
+
270
+ def _default_result_record(payload: Dict[str, Any]) -> Dict[str, Any]:
271
+ from supabase_client import mcp_result_record
272
+ return mcp_result_record(payload)
273
+
274
+
275
+ def write_completed_build(run_id: str, result_payload: Dict[str, Any], *,
276
+ opportunity: Any, environment: Any,
277
+ completed_at: Optional[str] = None,
278
+ connect: Callable[[], Any] = _default_connect,
279
+ fetch_intake: Callable[[str], Optional[Dict[str, Any]]] = _default_fetch_intake,
280
+ result_record: Callable[[Dict[str, Any]], Dict[str, Any]] = _default_result_record,
281
+ ) -> Dict[str, Any]:
282
+ """Put one just-completed build into the reporting tables, if it is a production
283
+ run. `result_payload` is the dict the server hands log_mcp_result. Returns
284
+ {"status": "written" | "skipped" | "failed", ...} and never raises: the server
285
+ calls this on a background thread after the build result is recorded, and
286
+ nothing here may fail, block or slow a build.
287
+ """
288
+ try:
289
+ if not is_production_run(opportunity, environment):
290
+ return {"status": "skipped", "reason": "not a production run",
291
+ "opportunity": str(opportunity or ""), "environment": str(environment or "")}
292
+ intake = fetch_intake(run_id)
293
+ if not intake or not intake.get("id") or not intake.get("created_at"):
294
+ # No intake row = the loader would never see this run either. Its
295
+ # own failure was already logged by log_mcp_payload.
296
+ return {"status": "skipped", "reason": "no intake row in demo_history"}
297
+ irf = intake.get("results") or {}
298
+ why = skip_reason(irf.get("owner_email"), intake.get("company_name"))
299
+ if why:
300
+ return {"status": "skipped", "reason": why}
301
+ run, res = shape_rows(intake, result_record(result_payload),
302
+ completed_at or datetime.now(timezone.utc).isoformat())
303
+ conn = connect()
304
+ try:
305
+ conn.autocommit(False)
306
+ cur = conn.cursor()
307
+ try:
308
+ out = write_run(cur, run, res)
309
+ finally:
310
+ cur.close()
311
+ conn.commit()
312
+ except Exception:
313
+ try:
314
+ conn.rollback()
315
+ except Exception:
316
+ pass
317
+ raise
318
+ finally:
319
+ try:
320
+ conn.close()
321
+ except Exception:
322
+ pass
323
+ return {"status": "written", "intake_id": run["INTAKE_ID"], **out}
324
+ except Exception as exc:
325
+ return {"status": "failed", "error": f"{type(exc).__name__}: {exc}"}
mcp_server.py CHANGED
@@ -787,6 +787,39 @@ def _complete_brand_from_capture(run_id: str, brand: dict, company_url: str,
787
  _log_hit(f"BRAND run_id={run_id} {n}")
788
 
789
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
  def _run_build(run_id: str, brief: str, company_name: str, use_case: str, company_url: str,
791
  ts_target_url: str, ts_auth_key: str, owner: str,
792
  with_portal: bool = True, call_payload: dict | None = None,
@@ -963,7 +996,7 @@ def _run_build(run_id: str, brief: str, company_name: str, use_case: str, compan
963
  # Durable result record: the in-memory stores above wipe on restart, so
964
  # every terminal outcome (success | partial | failed) is also persisted
965
  # to demo_history (status='mcp_result', joined to intake by run_id).
966
- _persisted_result = log_mcp_result({
967
  # The same Space + commit stamp the intake row and the session_logs
968
  # 'run started' event carry, so the three records agree on where
969
  # this run happened.
@@ -985,8 +1018,18 @@ def _run_build(run_id: str, brief: str, company_name: str, use_case: str, compan
985
  # schema if ThoughtSpot refused it (E3).
986
  "ddl": out["ddl"],
987
  **_opt_in,
988
- })
 
989
  _log_hit(f"RESULT_PERSISTED run_id={run_id} status={out['status']} supabase={_persisted_result}")
 
 
 
 
 
 
 
 
 
990
  return out
991
 
992
  controller = None
 
787
  _log_hit(f"BRAND run_id={run_id} {n}")
788
 
789
 
790
+ def _start_reporting_write_through(run_id: str, result_payload: dict, *,
791
+ opportunity: str, environment: str):
792
+ """Write a just-completed PRODUCTION run to the Snowflake reporting tables on a
793
+ daemon thread (demoprep_app.reporting.writer.write_completed_build). A
794
+ production run is a real Salesforce opportunity id (15/18 alphanumerics, no
795
+ TEST-/REPLAY-/XXTEST marker) with environment prod; anything else is left to
796
+ the hand-run loader, as before. Returns the started Thread, or None when the
797
+ run does not qualify or the thread could not be started. Never raises and
798
+ never waits: the build result is already recorded when this runs.
799
+ """
800
+ try:
801
+ from demoprep_app.reporting.writer import is_production_run, write_completed_build
802
+ if not is_production_run(opportunity, environment):
803
+ _log_hit(f"REPORTING_SKIP run_id={run_id} not a production run "
804
+ f"(opportunity={opportunity!r} environment={environment!r})")
805
+ return None
806
+
807
+ def _write() -> None:
808
+ try:
809
+ outcome = write_completed_build(run_id, result_payload,
810
+ opportunity=opportunity, environment=environment)
811
+ except Exception as exc: # write_completed_build never raises; belt and braces
812
+ outcome = {"status": "failed", "error": f"{type(exc).__name__}: {exc}"}
813
+ _log_hit(f"REPORTING_WRITE run_id={run_id} {outcome}")
814
+
815
+ t = threading.Thread(target=_write, daemon=True, name=f"reporting-{run_id}")
816
+ t.start()
817
+ return t
818
+ except Exception as exc:
819
+ _log_hit(f"REPORTING_WRITE run_id={run_id} not started: {type(exc).__name__}: {exc}")
820
+ return None
821
+
822
+
823
  def _run_build(run_id: str, brief: str, company_name: str, use_case: str, company_url: str,
824
  ts_target_url: str, ts_auth_key: str, owner: str,
825
  with_portal: bool = True, call_payload: dict | None = None,
 
996
  # Durable result record: the in-memory stores above wipe on restart, so
997
  # every terminal outcome (success | partial | failed) is also persisted
998
  # to demo_history (status='mcp_result', joined to intake by run_id).
999
+ result_payload = {
1000
  # The same Space + commit stamp the intake row and the session_logs
1001
  # 'run started' event carry, so the three records agree on where
1002
  # this run happened.
 
1018
  # schema if ThoughtSpot refused it (E3).
1019
  "ddl": out["ddl"],
1020
  **_opt_in,
1021
+ }
1022
+ _persisted_result = log_mcp_result(result_payload)
1023
  _log_hit(f"RESULT_PERSISTED run_id={run_id} status={out['status']} supabase={_persisted_result}")
1024
+ # Snowflake reporting write-through (boone 2026-10-26): a PRODUCTION run — a
1025
+ # real Salesforce opportunity id and environment prod — reaches the
1026
+ # reporting tables now, on its own thread, instead of waiting for the
1027
+ # hand-run loader. Every other run stays Supabase-only for the loader.
1028
+ # Starts after the durable records above and can never fail, block or
1029
+ # slow this result.
1030
+ _start_reporting_write_through(run_id, result_payload,
1031
+ opportunity=salesforce_opportunity_id,
1032
+ environment=environment)
1033
  return out
1034
 
1035
  controller = None
sprint_2026_04.md CHANGED
@@ -1847,6 +1847,76 @@ settings IN the database through the MCP server, dropdown-constrained.
1847
  real `TS_USER_DEFAULT` / `TS_ENV_URL_DEFAULT`) and the `OPENAI_API_KEY` check in
1848
  `validate_required_settings()`
1849
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1850
  ---
1851
 
1852
  *Last updated: October 22, 2026 — settings document on test-mcp (0d02061) and folded
 
1847
  real `TS_USER_DEFAULT` / `TS_ENV_URL_DEFAULT`) and the `OPENAI_API_KEY` check in
1848
  `validate_required_settings()`
1849
 
1850
+ ## October 26, 2026 — HOTFIX: production runs written to Snowflake reporting the moment a build completes (branch `claude/hotfix-snowflake-writethrough-d22061`, off the test tip `0bcf580`)
1851
+
1852
+ Found tonight: the tables the DemoPrep reporting liveboard reads
1853
+ (`DEMOPREP_REPORTING.REPORTING.DEMO_RUN` / `DEMO_RESULTS`, view `V_DEMO_RUN`) were
1854
+ filled ONLY by the hand-run loader `dev_notes/reporting_load_runs.py.txt` (reads the
1855
+ build log in Supabase: `demo_history` rows `mcp_intake` + `mcp_result`, MERGEs into
1856
+ Snowflake). No scheduler. Nobody ran it Oct 22–26, so five days of SE builds were
1857
+ missing until boone ran it by hand (DEMO_RUN then 570 rows, through Oct 26 20:46 UTC).
1858
+ CEO demo on Oct 27 — hotfixed tonight, through test and then prod (boone's call).
1859
+
1860
+ **The rule (boone):** a *production run* = `salesforce_opportunity_id` is a real
1861
+ Salesforce id (15 or 18 alphanumerics — the loader's `SFID`; `TEST-` / `REPLAY-` /
1862
+ `XXTEST` markers never qualify) AND `environment` is prod (`prod` / `production`;
1863
+ `staging` / `stage` do not). Those reach Snowflake the moment the build completes.
1864
+ Every other run keeps the old behaviour: Supabase only, picked up by the loader later.
1865
+
1866
+ Line decision: prod `b918bd8` is an ancestor of test `0bcf580` (`git merge-base
1867
+ --is-ancestor`), both confirmed live by `ping`; the test tip had completed a full
1868
+ successful build tonight (run `4de504402486`, liveboard + portal) and the sprint
1869
+ entries above record the Oct 22 pieces verified on test — so the normal path: branch
1870
+ from the test tip, deploy to test, verify, push the same SHA to prod.
1871
+
1872
+ ### Done
1873
+ - [x] `demoprep_app/reporting/writer.py` ✅ the loader's single-run logic, importable.
1874
+ Every loader rule kept verbatim: `SFID`, `TEST_MARKER`, the `INTERNAL` company set,
1875
+ the `testrunner` / `no.such.person` / `IGNORE` skips, `IS_TEST_RUN` = owner is
1876
+ mike.boone (sticky in the MERGE), `COALESCE` so a human-entered opportunity id is
1877
+ never overwritten with NULL, `err_bucket` / `cluster` / `space_of`, the model URL
1878
+ derived from ts_url + model guid. `LOAD_LEDGER` row keyed by `SOURCE_INTAKE_ID`
1879
+ (the `demo_history` intake row id) so the loader's next run finds it and UPDATEs
1880
+ the same `DEMO_RUN_KEY` — never a second row. One transaction, fully-qualified
1881
+ statements (no `USE SCHEMA`), `get_snowflake_connection()` (keypair from admin
1882
+ settings, which the Space already has). `write_completed_build()` never raises.
1883
+ Left to the loader: opportunity-id inheritance (moot — a write-through run always
1884
+ has one) and the app-findings sync into `DEMO_FINDINGS`.
1885
+ - [x] `supabase_client.fetch_mcp_intake_row(run_id)` ✅ the intake row AS STORED (id +
1886
+ created_at — `fetch_mcp_run` drops both); `mcp_result_record(payload)` ✅ the result
1887
+ row's `results`, now ONE shaping shared by `log_mcp_result` and the writer, so the
1888
+ write-through and the loader read the same record.
1889
+ - [x] `mcp_server._start_reporting_write_through()` ✅ called in `_run_build.result()`
1890
+ right after `log_mcp_result`, only for qualifying runs; daemon thread; try/except
1891
+ around everything; hit-log lines `REPORTING_SKIP` / `REPORTING_WRITE`. No MCP tool
1892
+ signature changed (AgentSpot caches the tool list). `.env` untouched.
1893
+ - [x] Tests ✅ `tests/test_reporting_writer.py` (48): the rule (real id + prod → yes;
1894
+ real id + staging → no; `TEST-NOT-A-DEAL` → no; blank → no …), the rows, the
1895
+ statements against a fake cursor (insert → ledger → merge; ledger hit → update with
1896
+ COALESCE), the orchestration (staging never connects; Snowflake failure → rolled
1897
+ back, reported, never raised), the server hook (own thread; a raising writer never
1898
+ escapes). `tests/test_deploy_existing_schema.py` source check re-pointed at
1899
+ `mcp_result_record`.
1900
+ - [x] SQL proven against real Snowflake in a rolled-back transaction ✅ insert →
1901
+ the row visible in `V_DEMO_RUN` with liveboard / model URL / portal / schema →
1902
+ second pass with the same intake id = `updated`, same key → rollback left nothing.
1903
+ - [ ] test-mcp: deploy, `ping` shows the SHA, e2e (prod-flagged build with a
1904
+ well-formed id + staging control), loader dry-run + real run with no double count
1905
+ - [ ] prod: same SHA, `ping`, one real-shaped prod build as the final proof
1906
+
1907
+ ### Open
1908
+ - [ ] **Safety net:** a scheduled loader run (daily) for everything the
1909
+ write-through does not cover — staging / test-marked runs that should still be
1910
+ reported, a run whose Snowflake write failed, the opportunity-id inheritance and
1911
+ the app-findings sync.
1912
+ - [ ] Loader quirk found on the way (not changed — the loader lives outside the repo):
1913
+ in its `DEMO_RESULTS` MERGE the `-- sticky …` comment swallows
1914
+ `SOURCE_LOADED_AT=CURRENT_TIMESTAMP(),` on the same line, so an UPDATE never
1915
+ refreshes `SOURCE_LOADED_AT` (inserts do). The writer's copy has the comment on its
1916
+ own line.
1917
+ - [ ] `dev_notes/reporting_admin_handoff_DEMO_RUN.md` "How it is maintained" should name
1918
+ the write-through beside the loader (the file is untracked in this line).
1919
+
1920
  ---
1921
 
1922
  *Last updated: October 22, 2026 — settings document on test-mcp (0d02061) and folded
supabase_client.py CHANGED
@@ -823,6 +823,44 @@ def fetch_mcp_run(run_id: str) -> Optional[Dict[str, Any]]:
823
  return None
824
 
825
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
826
  def log_mcp_payload(payload: Dict[str, Any]) -> bool:
827
  """Durably persist a raw MCP intake payload (the full brief + metadata) so real
828
  research payloads survive container restarts / log rotation — i.e. so you can tell
@@ -874,6 +912,69 @@ def log_mcp_payload(payload: Dict[str, Any]) -> bool:
874
  return False
875
 
876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
877
  def log_mcp_result(payload: Dict[str, Any]) -> bool:
878
  """Durably persist an MCP build RESULT (terminal status, object GUIDs/URLs,
879
  errors, timing) so outcomes survive container restarts — the in-memory
@@ -898,61 +999,10 @@ def log_mcp_result(payload: Dict[str, Any]) -> bool:
898
  "industry": "",
899
  "use_case": _history_text(payload.get("use_case")),
900
  "demo_config": {"source": "mcp_result"},
901
- "results": {
902
- "run_id": payload.get("run_id"),
903
- "build_status": payload.get("status"),
904
- # Which deployment produced this result: the HF Space and the
905
- # commit it ran (demoprep_app.deployment_identity). The intake
906
- # row and the session_logs 'run started' event carry the same
907
- # four keys, so a run ties to a Space and a build by lookup,
908
- # not by inference from result shapes.
909
- "space_id": payload.get("space_id") or "",
910
- "space_host": payload.get("space_host") or "",
911
- "code": payload.get("code") or "",
912
- "code_source": payload.get("code_source") or "",
913
- "owner_email": payload.get("owner_email"),
914
- "ts_url": payload.get("ts_url"),
915
- "schema": payload.get("schema"),
916
- "model_guid": payload.get("model_guid"),
917
- "liveboard_guid": payload.get("liveboard_guid"),
918
- "model_url": payload.get("model_url"),
919
- "liveboard_url": payload.get("liveboard_url"),
920
- # The branded customer-facing site built from the liveboard. This
921
- # is the deliverable an SE actually sends, so it must be
922
- # recoverable from the durable record and not only from the
923
- # return value of the call that created it.
924
- "portal_url": payload.get("portal_url") or "",
925
- # The editable-source zip beside it. Same reasoning: the SE who
926
- # wants to tailor the site needs to find this link later, not
927
- # only in the return value of the call that built it.
928
- "download_url": payload.get("download_url") or "",
929
- "warnings": payload.get("warnings") or [],
930
- # The structured records behind the warnings (code, severity,
931
- # measure, fix) — see demoprep_app/quality/findings.py.
932
- "findings": payload.get("findings") or [],
933
- # Did they reach demo_findings? None = nothing to persist,
934
- # False = the write failed (RLS 42501 until an insert policy
935
- # exists) and the records exist only here and in the run log.
936
- "findings_persisted": payload.get("findings_persisted"),
937
- "findings_persist_error": payload.get("findings_persist_error") or "",
938
- "errors": payload.get("errors") or [],
939
- "elapsed_seconds": payload.get("elapsed_seconds"),
940
- # The pre-build login check (mcp_server._login_precheck, ISS-22):
941
- # outcome, HTTP status, reason. None for runs from before it existed.
942
- "login_precheck": payload.get("login_precheck"),
943
- # The CREATE TABLE text the build loaded, so a run that died at
944
- # the ThoughtSpot step can be finished later from its schema
945
- # (mcp_server.deploy_existing_schema) without regenerating.
946
- "ddl": payload.get("ddl") or "",
947
- "source": "mcp",
948
- },
949
  "status": "mcp_result",
950
  "created_at": datetime.utcnow().isoformat(),
951
  }
952
- # Opt-in: the custom MCP demo walkthrough (markdown) is stored only for a
953
- # build that asked for it (custom_mcp_demo), so every other row is unchanged.
954
- if "custom_mcp_demo_section" in payload:
955
- row["results"]["custom_mcp_demo_section"] = payload["custom_mcp_demo_section"] or ""
956
  client.client.table("demo_history").insert(row).execute()
957
  return True
958
  except Exception as e:
 
823
  return None
824
 
825
 
826
+ def fetch_mcp_intake_row(run_id: str) -> Optional[Dict[str, Any]]:
827
+ """The newest demo_history intake row (status='mcp_intake') of one MCP run, AS
828
+ STORED: {id, created_at, company_name, use_case, results}. Unlike fetch_mcp_run
829
+ this keeps the row's own id and created_at — the id is what the reporting
830
+ loader's LOAD_LEDGER records as SOURCE_INTAKE_ID, so the Snowflake write-through
831
+ (demoprep_app.reporting.writer) and the loader agree on which request a
832
+ DEMO_RUN row belongs to, and created_at is that row's REQUESTED_AT. None when
833
+ Supabase is off, there is no such row, or the lookup fails — never raises.
834
+ """
835
+ try:
836
+ client = SupabaseSettings()
837
+ if not getattr(client, "enabled", False) or not (run_id or "").strip():
838
+ return None
839
+ resp = (
840
+ client.client.table("demo_history")
841
+ .select("id,created_at,company_name,use_case,results")
842
+ .eq("status", "mcp_intake")
843
+ .eq("results->>run_id", run_id.strip())
844
+ .order("created_at", desc=True)
845
+ .limit(1)
846
+ .execute()
847
+ )
848
+ rows = getattr(resp, "data", None) or []
849
+ if not rows:
850
+ return None
851
+ row = dict(rows[0])
852
+ if isinstance(row.get("results"), str):
853
+ try:
854
+ row["results"] = json.loads(row["results"])
855
+ except Exception:
856
+ row["results"] = {}
857
+ row["results"] = row.get("results") or {}
858
+ return row
859
+ except Exception as e:
860
+ print(f"⚠️ mcp intake lookup failed for {run_id!r}: {e}", flush=True)
861
+ return None
862
+
863
+
864
  def log_mcp_payload(payload: Dict[str, Any]) -> bool:
865
  """Durably persist a raw MCP intake payload (the full brief + metadata) so real
866
  research payloads survive container restarts / log rotation — i.e. so you can tell
 
912
  return False
913
 
914
 
915
+ def mcp_result_record(payload: Dict[str, Any]) -> Dict[str, Any]:
916
+ """The `results` JSON of an mcp_result row, shaped from a build's result payload.
917
+
918
+ ONE shaping for its two readers: log_mcp_result stores it, and the Snowflake
919
+ reporting write-through (demoprep_app.reporting.writer) reads this same dict
920
+ the moment a build completes — exactly what the hand-run loader reads back from
921
+ the row later, so the two never disagree about a run.
922
+ """
923
+ results = {
924
+ "run_id": payload.get("run_id"),
925
+ "build_status": payload.get("status"),
926
+ # Which deployment produced this result: the HF Space and the
927
+ # commit it ran (demoprep_app.deployment_identity). The intake
928
+ # row and the session_logs 'run started' event carry the same
929
+ # four keys, so a run ties to a Space and a build by lookup,
930
+ # not by inference from result shapes.
931
+ "space_id": payload.get("space_id") or "",
932
+ "space_host": payload.get("space_host") or "",
933
+ "code": payload.get("code") or "",
934
+ "code_source": payload.get("code_source") or "",
935
+ "owner_email": payload.get("owner_email"),
936
+ "ts_url": payload.get("ts_url"),
937
+ "schema": payload.get("schema"),
938
+ "model_guid": payload.get("model_guid"),
939
+ "liveboard_guid": payload.get("liveboard_guid"),
940
+ "model_url": payload.get("model_url"),
941
+ "liveboard_url": payload.get("liveboard_url"),
942
+ # The branded customer-facing site built from the liveboard. This
943
+ # is the deliverable an SE actually sends, so it must be
944
+ # recoverable from the durable record and not only from the
945
+ # return value of the call that created it.
946
+ "portal_url": payload.get("portal_url") or "",
947
+ # The editable-source zip beside it. Same reasoning: the SE who
948
+ # wants to tailor the site needs to find this link later, not
949
+ # only in the return value of the call that built it.
950
+ "download_url": payload.get("download_url") or "",
951
+ "warnings": payload.get("warnings") or [],
952
+ # The structured records behind the warnings (code, severity,
953
+ # measure, fix) — see demoprep_app/quality/findings.py.
954
+ "findings": payload.get("findings") or [],
955
+ # Did they reach demo_findings? None = nothing to persist,
956
+ # False = the write failed (RLS 42501 until an insert policy
957
+ # exists) and the records exist only here and in the run log.
958
+ "findings_persisted": payload.get("findings_persisted"),
959
+ "findings_persist_error": payload.get("findings_persist_error") or "",
960
+ "errors": payload.get("errors") or [],
961
+ "elapsed_seconds": payload.get("elapsed_seconds"),
962
+ # The pre-build login check (mcp_server._login_precheck, ISS-22):
963
+ # outcome, HTTP status, reason. None for runs from before it existed.
964
+ "login_precheck": payload.get("login_precheck"),
965
+ # The CREATE TABLE text the build loaded, so a run that died at
966
+ # the ThoughtSpot step can be finished later from its schema
967
+ # (mcp_server.deploy_existing_schema) without regenerating.
968
+ "ddl": payload.get("ddl") or "",
969
+ "source": "mcp",
970
+ }
971
+ # Opt-in: the custom MCP demo walkthrough (markdown) is stored only for a
972
+ # build that asked for it (custom_mcp_demo), so every other row is unchanged.
973
+ if "custom_mcp_demo_section" in payload:
974
+ results["custom_mcp_demo_section"] = payload["custom_mcp_demo_section"] or ""
975
+ return results
976
+
977
+
978
  def log_mcp_result(payload: Dict[str, Any]) -> bool:
979
  """Durably persist an MCP build RESULT (terminal status, object GUIDs/URLs,
980
  errors, timing) so outcomes survive container restarts — the in-memory
 
999
  "industry": "",
1000
  "use_case": _history_text(payload.get("use_case")),
1001
  "demo_config": {"source": "mcp_result"},
1002
+ "results": mcp_result_record(payload),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1003
  "status": "mcp_result",
1004
  "created_at": datetime.utcnow().isoformat(),
1005
  }
 
 
 
 
1006
  client.client.table("demo_history").insert(row).execute()
1007
  return True
1008
  except Exception as e:
tests/test_deploy_existing_schema.py CHANGED
@@ -66,7 +66,10 @@ def test_the_ddl_is_kept_on_the_run_records_but_left_out_of_status():
66
  # _last_build (always had it) + _recent_builds + log_mcp_result
67
  assert '"ddl": out["ddl"],' in src and src.count('"ddl": out["ddl"]') == 3
68
  assert 'if k != "ddl"' in inspect.getsource(mcp_server.status)
69
- sb = inspect.getsource(supabase_client.log_mcp_result)
 
 
 
70
  assert '"ddl": payload.get("ddl") or ""' in sb
71
 
72
 
 
66
  # _last_build (always had it) + _recent_builds + log_mcp_result
67
  assert '"ddl": out["ddl"],' in src and src.count('"ddl": out["ddl"]') == 3
68
  assert 'if k != "ddl"' in inspect.getsource(mcp_server.status)
69
+ # The row's `results` are shaped by mcp_result_record (shared with the Snowflake
70
+ # reporting write-through since 2026-10-26); log_mcp_result stores that dict.
71
+ assert '"results": mcp_result_record(payload)' in inspect.getsource(supabase_client.log_mcp_result)
72
+ sb = inspect.getsource(supabase_client.mcp_result_record)
73
  assert '"ddl": payload.get("ddl") or ""' in sb
74
 
75
 
tests/test_portal_url_round_trip.py CHANGED
@@ -27,6 +27,25 @@ def _build_result_source() -> str:
27
  return inspect.getsource(mcp_server._run_build)
28
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def test_status_view_carries_portal_url():
31
  """_recent_builds is what status() serves; it must include the site."""
32
  src = _mcp_source()
@@ -36,15 +55,13 @@ def test_status_view_carries_portal_url():
36
 
37
  def test_durable_result_payload_carries_portal_url():
38
  """The payload handed to log_mcp_result must include the site."""
39
- src = _build_result_source()
40
- block = src.split("log_mcp_result({", 1)[1].split("})", 1)[0]
41
  assert "portal_url" in block, "the durable record would omit the built site"
42
 
43
 
44
  def test_persisted_row_carries_portal_url():
45
  """log_mcp_result rebuilds `results` from its own key list — check it too."""
46
- from supabase_client import log_mcp_result
47
- src = inspect.getsource(log_mcp_result)
48
  assert '"portal_url"' in src, "demo_history rows would omit the built site"
49
 
50
 
@@ -73,12 +90,10 @@ def test_status_view_carries_download_url():
73
 
74
 
75
  def test_durable_result_payload_carries_download_url():
76
- src = _build_result_source()
77
- block = src.split("log_mcp_result({", 1)[1].split("})", 1)[0]
78
  assert "download_url" in block, "the durable record would omit the source zip"
79
 
80
 
81
  def test_persisted_row_carries_download_url():
82
- from supabase_client import log_mcp_result
83
- src = inspect.getsource(log_mcp_result)
84
  assert '"download_url"' in src, "demo_history rows would omit the source zip"
 
27
  return inspect.getsource(mcp_server._run_build)
28
 
29
 
30
+ def _durable_payload_block() -> str:
31
+ """The dict _run_build hands log_mcp_result. Since the Snowflake reporting
32
+ write-through (2026-10-26) it is built first as `result_payload` and then
33
+ stored — and handed to the write-through — so the block is that literal."""
34
+ src = _build_result_source()
35
+ assert "_persisted_result = log_mcp_result(result_payload)" in src, \
36
+ "the durable record is no longer the result_payload dict"
37
+ return src.split("result_payload = {", 1)[1].split("_persisted_result = log_mcp_result(result_payload)", 1)[0]
38
+
39
+
40
+ def _persisted_row_source() -> str:
41
+ """The `results` row log_mcp_result stores is shaped by mcp_result_record
42
+ (shared with the write-through) — check the shaping, and that it is stored."""
43
+ from supabase_client import log_mcp_result, mcp_result_record
44
+ assert '"results": mcp_result_record(payload)' in inspect.getsource(log_mcp_result), \
45
+ "log_mcp_result no longer stores the mcp_result_record row"
46
+ return inspect.getsource(mcp_result_record)
47
+
48
+
49
  def test_status_view_carries_portal_url():
50
  """_recent_builds is what status() serves; it must include the site."""
51
  src = _mcp_source()
 
55
 
56
  def test_durable_result_payload_carries_portal_url():
57
  """The payload handed to log_mcp_result must include the site."""
58
+ block = _durable_payload_block()
 
59
  assert "portal_url" in block, "the durable record would omit the built site"
60
 
61
 
62
  def test_persisted_row_carries_portal_url():
63
  """log_mcp_result rebuilds `results` from its own key list — check it too."""
64
+ src = _persisted_row_source()
 
65
  assert '"portal_url"' in src, "demo_history rows would omit the built site"
66
 
67
 
 
90
 
91
 
92
  def test_durable_result_payload_carries_download_url():
93
+ block = _durable_payload_block()
 
94
  assert "download_url" in block, "the durable record would omit the source zip"
95
 
96
 
97
  def test_persisted_row_carries_download_url():
98
+ src = _persisted_row_source()
 
99
  assert '"download_url"' in src, "demo_history rows would omit the source zip"
tests/test_reporting_writer.py ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The Snowflake reporting write-through: a production run reaches DEMO_RUN /
2
+ DEMO_RESULTS the moment its build completes; everything else is left to the loader.
3
+
4
+ Run: pytest tests/test_reporting_writer.py --noconftest
5
+
6
+ Three layers, none touching Snowflake or Supabase:
7
+ 1. the rule (boone 2026-10-26): real Salesforce id AND environment prod;
8
+ 2. the rows and statements, against a fake cursor — the same semantics as the
9
+ hand-run loader (dev_notes/reporting_load_runs.py.txt): ledger lookup,
10
+ COALESCE on the opportunity id, sticky IS_TEST_RUN, the derived model URL;
11
+ 3. the server hook: it never runs for a non-production run, runs on its own
12
+ thread for a production run, and a failing writer never escapes.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import threading
18
+
19
+ os.environ.setdefault("MCP_TRANSPORT", "stdio")
20
+
21
+ import pytest
22
+
23
+ from demoprep_app.reporting import writer as W
24
+
25
+ REAL_18 = "006Hs00000AbCdEfQA" # well-formed: 18 alphanumerics
26
+ REAL_15 = "006Hs00000AbCdE" # well-formed: 15 alphanumerics
27
+ MIKE = "mike.boone@thoughtspot.com"
28
+
29
+
30
+ # --- 1. the rule ---------------------------------------------------------------- #
31
+ @pytest.mark.parametrize("opportunity, environment, expected", [
32
+ (REAL_18, "prod", True),
33
+ (REAL_15, "prod", True),
34
+ (REAL_18, "production", True),
35
+ (REAL_18, " Prod ", True),
36
+ (REAL_18, "staging", False),
37
+ (REAL_18, "stage", False),
38
+ ("TEST-NOT-A-DEAL", "prod", False),
39
+ ("REPLAY-NOT-A-DEAL-ACME", "prod", False),
40
+ ("XXTEST000000000001", "prod", False),
41
+ ("xxtest000000000001", "prod", False), # the marker is case-insensitive
42
+ ("", "prod", False),
43
+ (None, "prod", False),
44
+ (" ", "prod", False),
45
+ ("006Hs00000", "prod", False), # wrong length
46
+ ("006Hs00000AbCdEfQA1", "prod", False), # 19 characters
47
+ ("006Hs00000-bCdEfQA", "prod", False), # not alphanumeric
48
+ (REAL_18, "", False),
49
+ (REAL_18, None, False),
50
+ (REAL_18, "dev", False),
51
+ ])
52
+ def test_is_production_run(opportunity, environment, expected):
53
+ assert W.is_production_run(opportunity, environment) is expected
54
+
55
+
56
+ def test_normalize_environment_matches_the_tool_synonyms():
57
+ assert [W.normalize_environment(v) for v in ("prod", "PRODUCTION", "staging", "Stage", "qa", "")] == \
58
+ ["prod", "prod", "staging", "staging", "", ""]
59
+
60
+
61
+ def test_opportunity_id_is_the_loaders_opp_id():
62
+ assert W.opportunity_id(f" {REAL_18} ") == REAL_18
63
+ assert W.opportunity_id("NONE") is None
64
+ assert W.opportunity_id("") is None
65
+
66
+
67
+ @pytest.mark.parametrize("owner, company, reason", [
68
+ ("", "Acme", "no owner"),
69
+ ("testrunner@thoughtspot.com", "Acme", "testrunner owner"),
70
+ ("no.such.person.42@thoughtspot.com", "Acme", "no.such.person owner"),
71
+ ("sam@thoughtspot.com", "Acme IGNORE", "IGNORE company"),
72
+ ("sam@thoughtspot.com", "Northwind Retail", "internal company"),
73
+ ("sam@thoughtspot.com", "Acme", None),
74
+ (MIKE, "Acme", None),
75
+ ])
76
+ def test_skip_reason_is_the_loaders_filter(owner, company, reason):
77
+ assert W.skip_reason(owner, company) == reason
78
+
79
+
80
+ # --- 2. the rows ------------------------------------------------------------------ #
81
+ def _intake(owner=MIKE, company="Acme Logistics", opp=REAL_18, space="thoughtspot-demoprep/mcp",
82
+ ts_url=""):
83
+ return {
84
+ "id": "a76030cc-b070-4653-9264-0a904eed2dae",
85
+ "created_at": "2026-10-27T00:07:22.393086+00:00",
86
+ "company_name": company,
87
+ "use_case": "Fleet utilisation analytics",
88
+ "results": {
89
+ "run_id": "4de504402486", "owner_email": owner, "ts_url": ts_url,
90
+ "salesforce_opportunity_id": opp, "environment": "prod",
91
+ "space_id": space, "code": "0bcf58064368", "brief": "...",
92
+ },
93
+ }
94
+
95
+
96
+ def _result(status="success", errors=(), model_url="", ts="https://se-thoughtspot-cloud.thoughtspot.cloud"):
97
+ return {
98
+ "run_id": "4de504402486", "build_status": status, "owner_email": MIKE,
99
+ "ts_url": ts, "schema": "ACME_LOGISTICS_FLEET_20261027",
100
+ "model_guid": "11111111-2222-3333-4444-555555555555",
101
+ "liveboard_guid": "66666666-7777-8888-9999-000000000000",
102
+ "model_url": model_url,
103
+ "liveboard_url": f"{ts}/#/pinboard/66666666-7777-8888-9999-000000000000",
104
+ "portal_url": " https://thoughtspot-demoprep-portals.static.hf.space/acme/ ",
105
+ "errors": list(errors), "elapsed_seconds": 912.4,
106
+ }
107
+
108
+
109
+ def test_shape_rows_mirrors_the_loader_for_a_success():
110
+ run, res = W.shape_rows(_intake(), _result(), "2026-10-27T00:22:40+00:00")
111
+ assert run == {"INTAKE_ID": "a76030cc-b070-4653-9264-0a904eed2dae", "RUN_ID": "4de504402486",
112
+ "SFDC": REAL_18, "EMAIL": MIKE, "PROSPECT": "Acme Logistics"}
113
+ assert res["INTAKE_ID"] == run["INTAKE_ID"]
114
+ assert res["USE_CASE"] == "Fleet utilisation analytics"
115
+ assert res["REQUESTED_AT"] == "2026-10-27T00:07:22.393086+00:00"
116
+ assert res["COMPLETED_AT"] == "2026-10-27T00:22:40+00:00"
117
+ assert res["ELAPSED"] == 912.4
118
+ assert res["STATUS"] == "success" and res["ERR_CAT"] is None and res["ERR_TXT"] is None
119
+ assert res["SPACE"] == "prod" and res["CLUSTER_NAME"] == "se-thoughtspot-cloud"
120
+ assert res["IS_TEST"] is True # Mike's runs are test runs
121
+ assert res["LB_GUID"] == "66666666-7777-8888-9999-000000000000"
122
+ assert res["LB_URL"].endswith("/#/pinboard/66666666-7777-8888-9999-000000000000")
123
+ assert res["MODEL_GUID"] == "11111111-2222-3333-4444-555555555555"
124
+ # No model_url in the result: derived from the resolved ts_url + model guid, as the loader does.
125
+ assert res["MODEL_URL"] == ("https://se-thoughtspot-cloud.thoughtspot.cloud"
126
+ "/#/data/tables/11111111-2222-3333-4444-555555555555")
127
+ assert res["PORTAL_URL"] == "https://thoughtspot-demoprep-portals.static.hf.space/acme/" # stripped
128
+ assert res["SCHEMA_NAME"] == "ACME_LOGISTICS_FLEET_20261027"
129
+
130
+
131
+ def test_shape_rows_for_another_se_is_not_a_test_run_and_keeps_its_own_cluster():
132
+ run, res = W.shape_rows(_intake(owner="Sam.Jones@thoughtspot.com", space="thoughtspot-demoprep/test-mcp"),
133
+ _result(ts="https://sebe.thoughtspot.cloud"), "2026-10-27T00:22:40+00:00")
134
+ assert run["EMAIL"] == "sam.jones@thoughtspot.com" # lower-cased, as the loader stores it
135
+ assert res["IS_TEST"] is False
136
+ assert res["SPACE"] == "test" and res["CLUSTER_NAME"] == "sebe"
137
+
138
+
139
+ def test_shape_rows_for_a_failure_buckets_the_first_error():
140
+ _, res = W.shape_rows(_intake(), _result(status="failed", errors=["LLM call timed out after 600s", "x"]),
141
+ "2026-10-27T00:22:40+00:00")
142
+ assert res["STATUS"] == "failed"
143
+ assert res["ERR_CAT"] == "llm_timeout"
144
+ assert res["ERR_TXT"] == "LLM call timed out after 600s"
145
+
146
+
147
+ def test_shape_rows_with_no_result_record_is_no_result():
148
+ _, res = W.shape_rows(_intake(), {}, "2026-10-27T00:22:40+00:00")
149
+ assert res["STATUS"] == "no_result" and res["COMPLETED_AT"] is None and res["MODEL_URL"] is None
150
+
151
+
152
+ def test_shape_rows_rejects_a_malformed_opportunity_id_as_null_never_truncated():
153
+ run, _ = W.shape_rows(_intake(opp="006Hs00000AbCdEfQA-EXTRA"), _result(), None)
154
+ assert run["SFDC"] is None
155
+
156
+
157
+ # --- the statements, against a fake cursor -------------------------------------- #
158
+ class FakeCursor:
159
+ """Records every statement; answers the two SELECTs from `answers`."""
160
+
161
+ def __init__(self, ledger_key=None, inserted_key=777):
162
+ self.calls: list[tuple[str, tuple]] = []
163
+ self._ledger_key = ledger_key
164
+ self._inserted_key = inserted_key
165
+ self._next = None
166
+ self.closed = False
167
+
168
+ def execute(self, sql, params=None):
169
+ self.calls.append((sql, tuple(params or ())))
170
+ if sql == W.SQL_LEDGER_LOOKUP:
171
+ self._next = (self._ledger_key,) if self._ledger_key is not None else None
172
+ elif sql == W.SQL_RUN_KEY_OF_INSERT:
173
+ self._next = (self._inserted_key,)
174
+ else:
175
+ self._next = None
176
+
177
+ def fetchone(self):
178
+ return self._next
179
+
180
+ def close(self):
181
+ self.closed = True
182
+
183
+
184
+ def _rows():
185
+ return W.shape_rows(_intake(), _result(), "2026-10-27T00:22:40+00:00")
186
+
187
+
188
+ def test_a_new_request_is_inserted_then_ledgered_then_merged():
189
+ cur = FakeCursor(ledger_key=None, inserted_key=5131)
190
+ run, res = _rows()
191
+ out = W.write_run(cur, run, res)
192
+ assert out == {"demo_run_key": 5131, "action": "inserted"}
193
+ sqls = [c[0] for c in cur.calls]
194
+ assert sqls == [W.SQL_LEDGER_LOOKUP, W.SQL_RUN_INSERT, W.SQL_RUN_KEY_OF_INSERT,
195
+ W.SQL_LEDGER_INSERT, W.SQL_RESULTS_MERGE]
196
+ assert cur.calls[0][1] == (run["INTAKE_ID"],)
197
+ assert cur.calls[1][1] == ("4de504402486", REAL_18, MIKE, "Acme Logistics")
198
+ assert cur.calls[3][1] == (run["INTAKE_ID"], 5131) # the ledger row the loader will find
199
+ merge_params = cur.calls[4][1]
200
+ assert merge_params[0] == 5131
201
+ assert merge_params[1:] == tuple(res[k] for k in W._RES_ORDER)
202
+ assert len(merge_params) == 1 + 16 == W.SQL_RESULTS_MERGE.count("%s")
203
+
204
+
205
+ def test_a_request_the_ledger_knows_is_updated_not_inserted():
206
+ cur = FakeCursor(ledger_key=4242)
207
+ run, res = _rows()
208
+ out = W.write_run(cur, run, res)
209
+ assert out == {"demo_run_key": 4242, "action": "updated"}
210
+ sqls = [c[0] for c in cur.calls]
211
+ assert sqls == [W.SQL_LEDGER_LOOKUP, W.SQL_RUN_UPDATE, W.SQL_RESULTS_MERGE]
212
+ assert cur.calls[1][1] == ("4de504402486", REAL_18, MIKE, "Acme Logistics", 4242)
213
+ assert cur.calls[2][1][0] == 4242
214
+
215
+
216
+ def test_the_statements_keep_the_loaders_rules():
217
+ # a human-entered opportunity id is never overwritten with NULL
218
+ assert "SALESFORCE_OPPORTUNITY_ID=COALESCE(SALESFORCE_OPPORTUNITY_ID,%s)" in W.SQL_RUN_UPDATE
219
+ # IS_TEST_RUN is sticky; artifact links are COALESCEd; both branches of the MERGE exist
220
+ m = W.SQL_RESULTS_MERGE
221
+ assert "IS_TEST_RUN=(t.IS_TEST_RUN OR s.IS_TEST)" in m
222
+ for col in ("LIVEBOARD_GUID", "LIVEBOARD_URL", "MODEL_GUID", "MODEL_URL", "PORTAL_URL", "SNOWFLAKE_SCHEMA"):
223
+ assert f"{col}=COALESCE(" in m, col
224
+ assert "WHEN MATCHED THEN UPDATE" in m and "WHEN NOT MATCHED THEN INSERT" in m
225
+ # every statement names the reporting schema outright — no USE SCHEMA, no demo-database guess
226
+ for sql in (W.SQL_LEDGER_LOOKUP, W.SQL_RUN_UPDATE, W.SQL_RUN_INSERT, W.SQL_RUN_KEY_OF_INSERT,
227
+ W.SQL_LEDGER_INSERT, W.SQL_RESULTS_MERGE):
228
+ assert "DEMOPREP_REPORTING.REPORTING." in sql
229
+ # the ledger is keyed by the intake row, the FK the loader joins on
230
+ assert "SOURCE_INTAKE_ID" in W.SQL_LEDGER_LOOKUP and "SOURCE_INTAKE_ID" in W.SQL_LEDGER_INSERT
231
+
232
+
233
+ def test_an_insert_that_cannot_be_keyed_is_an_error_not_a_silent_half_row():
234
+ cur = FakeCursor(ledger_key=None, inserted_key=None)
235
+ run, res = _rows()
236
+ with pytest.raises(RuntimeError):
237
+ W.write_run(cur, run, res)
238
+ assert W.SQL_LEDGER_INSERT not in [c[0] for c in cur.calls]
239
+
240
+
241
+ # --- write_completed_build: the orchestration --------------------------------- #
242
+ class FakeConn:
243
+ def __init__(self, cur, fail=False):
244
+ self.cur, self.fail = cur, fail
245
+ self.events: list[str] = []
246
+
247
+ def autocommit(self, flag):
248
+ self.events.append(f"autocommit={flag}")
249
+
250
+ def cursor(self):
251
+ if self.fail:
252
+ raise RuntimeError("boom: Snowflake unreachable")
253
+ return self.cur
254
+
255
+ def commit(self):
256
+ self.events.append("commit")
257
+
258
+ def rollback(self):
259
+ self.events.append("rollback")
260
+
261
+ def close(self):
262
+ self.events.append("close")
263
+
264
+
265
+ def _payload():
266
+ return {"run_id": "4de504402486", "status": "success", "owner_email": MIKE,
267
+ "ts_url": "https://se-thoughtspot-cloud.thoughtspot.cloud",
268
+ "schema": "ACME", "model_guid": "m", "liveboard_guid": "l",
269
+ "model_url": "mu", "liveboard_url": "lu", "portal_url": "pu",
270
+ "errors": [], "elapsed_seconds": 10.0}
271
+
272
+
273
+ def test_a_production_run_is_written_and_committed():
274
+ cur = FakeCursor(ledger_key=None, inserted_key=5131)
275
+ conn = FakeConn(cur)
276
+ calls = {"connect": 0}
277
+
278
+ def connect():
279
+ calls["connect"] += 1
280
+ return conn
281
+
282
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="prod",
283
+ completed_at="2026-10-27T00:22:40+00:00",
284
+ connect=connect, fetch_intake=lambda rid: _intake(),
285
+ result_record=lambda p: _result())
286
+ assert out == {"status": "written", "intake_id": "a76030cc-b070-4653-9264-0a904eed2dae",
287
+ "demo_run_key": 5131, "action": "inserted"}
288
+ assert calls["connect"] == 1
289
+ assert conn.events == ["autocommit=False", "commit", "close"] and cur.closed
290
+
291
+
292
+ def test_a_staging_run_never_opens_a_connection():
293
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="staging",
294
+ connect=lambda: pytest.fail("connected for a staging run"),
295
+ fetch_intake=lambda rid: pytest.fail("looked up a staging run"))
296
+ assert out["status"] == "skipped" and out["reason"] == "not a production run"
297
+
298
+
299
+ def test_a_test_marked_run_never_opens_a_connection():
300
+ out = W.write_completed_build("4de504402486", _payload(), opportunity="TEST-NOT-A-DEAL", environment="prod",
301
+ connect=lambda: pytest.fail("connected for a test run"),
302
+ fetch_intake=lambda rid: pytest.fail("looked up a test run"))
303
+ assert out["status"] == "skipped"
304
+
305
+
306
+ def test_a_run_without_an_intake_row_is_skipped_like_the_loader_would():
307
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="prod",
308
+ connect=lambda: pytest.fail("connected with no intake row"),
309
+ fetch_intake=lambda rid: None)
310
+ assert out == {"status": "skipped", "reason": "no intake row in demo_history"}
311
+
312
+
313
+ def test_a_run_the_loader_would_skip_is_skipped_here_too():
314
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="prod",
315
+ connect=lambda: pytest.fail("connected for a testrunner run"),
316
+ fetch_intake=lambda rid: _intake(owner="testrunner@thoughtspot.com"))
317
+ assert out == {"status": "skipped", "reason": "testrunner owner"}
318
+
319
+
320
+ def test_a_snowflake_failure_is_reported_rolled_back_and_never_raised():
321
+ conn = FakeConn(FakeCursor(), fail=True)
322
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="prod",
323
+ connect=lambda: conn, fetch_intake=lambda rid: _intake(),
324
+ result_record=lambda p: _result())
325
+ assert out["status"] == "failed" and "Snowflake unreachable" in out["error"]
326
+ assert conn.events == ["autocommit=False", "rollback", "close"]
327
+
328
+
329
+ def test_a_connect_failure_is_reported_never_raised():
330
+ def connect():
331
+ raise ConnectionError("no route to Snowflake")
332
+ out = W.write_completed_build("4de504402486", _payload(), opportunity=REAL_18, environment="prod",
333
+ connect=connect, fetch_intake=lambda rid: _intake(),
334
+ result_record=lambda p: _result())
335
+ assert out == {"status": "failed", "error": "ConnectionError: no route to Snowflake"}
336
+
337
+
338
+ # --- 3. the server hook ------------------------------------------------------------ #
339
+ def test_the_hook_skips_a_non_production_run_without_a_thread(monkeypatch):
340
+ import mcp_server as M
341
+ monkeypatch.setattr(W, "write_completed_build",
342
+ lambda *a, **k: pytest.fail("the writer ran for a staging run"))
343
+ lines = []
344
+ monkeypatch.setattr(M, "_log_hit", lines.append)
345
+ t = M._start_reporting_write_through("abc", {"run_id": "abc"}, opportunity=REAL_18, environment="staging")
346
+ assert t is None
347
+ assert any("REPORTING_SKIP run_id=abc" in l for l in lines)
348
+
349
+
350
+ def test_the_hook_writes_a_production_run_on_its_own_thread(monkeypatch):
351
+ import mcp_server as M
352
+ seen = {}
353
+
354
+ def fake_write(run_id, payload, *, opportunity, environment):
355
+ seen.update(run_id=run_id, payload=payload, opportunity=opportunity, environment=environment,
356
+ thread=threading.current_thread().name)
357
+ return {"status": "written", "demo_run_key": 5131, "action": "inserted"}
358
+
359
+ monkeypatch.setattr(W, "write_completed_build", fake_write)
360
+ lines = []
361
+ monkeypatch.setattr(M, "_log_hit", lines.append)
362
+ t = M._start_reporting_write_through("abc", {"run_id": "abc", "status": "success"},
363
+ opportunity=REAL_18, environment="prod")
364
+ assert t is not None
365
+ t.join(5)
366
+ assert not t.is_alive()
367
+ assert seen["run_id"] == "abc" and seen["opportunity"] == REAL_18 and seen["environment"] == "prod"
368
+ assert seen["payload"] == {"run_id": "abc", "status": "success"}
369
+ assert seen["thread"] == "reporting-abc" and seen["thread"] != threading.current_thread().name
370
+ assert any("REPORTING_WRITE run_id=abc" in l and "'demo_run_key': 5131" in l for l in lines)
371
+
372
+
373
+ def test_a_writer_that_raises_never_escapes_the_hook(monkeypatch):
374
+ import mcp_server as M
375
+
376
+ def exploding(*a, **k):
377
+ raise RuntimeError("writer exploded")
378
+
379
+ monkeypatch.setattr(W, "write_completed_build", exploding)
380
+ lines = []
381
+ monkeypatch.setattr(M, "_log_hit", lines.append)
382
+ t = M._start_reporting_write_through("abc", {"run_id": "abc"}, opportunity=REAL_18, environment="prod")
383
+ t.join(5)
384
+ assert not t.is_alive()
385
+ assert any("REPORTING_WRITE run_id=abc" in l and "writer exploded" in l for l in lines)
386
+
387
+
388
+ def test_the_result_record_is_the_one_log_mcp_result_stores():
389
+ """The write-through reads supabase_client.mcp_result_record; the stored row's
390
+ `results` must be that same dict, or the loader would later disagree with it."""
391
+ import supabase_client as SC
392
+ from tests.test_payload_reaches_the_database import captured_result_row
393
+ payload = {**_payload(), "company_name": "Acme Logistics", "use_case": "Fleet", "warnings": ["w"],
394
+ "findings": [{"code": "F1"}], "custom_mcp_demo_section": "# demo"}
395
+ assert captured_result_row(payload) == SC.mcp_result_record(payload)
396
+ assert SC.mcp_result_record(payload)["build_status"] == "success"
397
+ assert SC.mcp_result_record(payload)["custom_mcp_demo_section"] == "# demo"
398
+ assert "custom_mcp_demo_section" not in SC.mcp_result_record(_payload())