fix: detect Mercury stage per-view via session name

The Mercury app (3.2.x) is a hybrid server whose kernel pool serves both
the client-facing app view and JupyterLab, so the server-level
MERCURY_CONFIG_DIR signal cannot tell who is looking and leaks backstage
content in the app. Switch the primary stage signal to the shadow-copy
session name (__mercury__ in JPY_SESSION_NAME), keeping the env var only
as a --working-dir fallback.

- Update CX Discovery staging.py to the per-view detection model
- Ignore Mercury runtime artifacts (.mercury_sessions/, *__mercury__*)
- Document the leak in remaining masters (TEI, CTM, AI Diagnostic,
  template) and flag the staging.py retrofit as urgent
This commit is contained in:
2026-07-31 17:30:30 +00:00
parent a967f73d09
commit 22a5d907d6
7 changed files with 3224 additions and 3141 deletions

View File

@@ -1,16 +1,27 @@
"""
Stage vs backstage — is this notebook render stakeholder-facing?
The Mercury CLI (``mercury --working-dir …``) exports ``MERCURY_CONFIG_DIR``
into the server process so the widget library can locate ``config.toml``
(see ``mercury/config.py``); every kernel that server spawns inherits it.
JupyterLab and nbconvert kernels don't have it. That makes the variable a
reliable signal for "the audience is looking" (the stage) versus an
analyst session or a headless export run (backstage).
The Mercury app (3.2.x) is a hybrid Jupyter server: the SAME server (and
kernel pool) can serve both the client-facing app view and JupyterLab, so a
server-level signal cannot tell who is looking. The reliable, per-view
signal is the session name: the app runs every session against a shadow
copy named ``<notebook>__mercury__<id>.ipynb``
(``mercury_app/handlers.py``), and the kernel sees that path in
``JPY_SESSION_NAME``. JupyterLab and nbconvert sessions carry the plain
notebook path (or no session name at all).
Diagnostics routed through :func:`backstage` stay visible in JupyterLab
and land in the nbconvert exports (where the machine-readable appendix
must appear for LLM consumption) but never render in the Mercury app.
``MERCURY_CONFIG_DIR`` is kept as a fallback: ``mercury --working-dir …``
exports it into the server process and every kernel inherits it. It is a
server-level signal — on such a server even JupyterLab kernels carry it, so
diagnostics are then hidden in that Lab view too (hidden, never leaked; use
a separate ``jupyter lab`` for analysis, which is the normal workflow). It
is NOT set when mercury is launched without ``--working-dir``, which is why
it cannot be the primary signal.
Diagnostics routed through :func:`backstage` / :func:`backstage_md` stay
visible in JupyterLab and land in the nbconvert exports (where the
machine-readable appendix must appear for LLM consumption) but never render
in the Mercury app.
"""
from __future__ import annotations
@@ -20,7 +31,9 @@ from typing import Any
def on_stage() -> bool:
"""True when running under the Mercury app (stakeholder-facing)."""
"""True when this kernel renders the client-facing Mercury app view."""
if "__mercury__" in os.getenv("JPY_SESSION_NAME", ""):
return True # the app's shadow-copy session
return os.getenv("MERCURY_CONFIG_DIR") is not None