matrx-connect 0.1.73__tar.gz

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 (81) hide show
  1. matrx_connect-0.1.73/.gitignore +299 -0
  2. matrx_connect-0.1.73/AGENTS.md +1 -0
  3. matrx_connect-0.1.73/CLAUDE.md +285 -0
  4. matrx_connect-0.1.73/PKG-INFO +118 -0
  5. matrx_connect-0.1.73/README.md +89 -0
  6. matrx_connect-0.1.73/matrx_connect/__init__.py +88 -0
  7. matrx_connect-0.1.73/matrx_connect/chat_timing.py +494 -0
  8. matrx_connect-0.1.73/matrx_connect/context/__init__.py +348 -0
  9. matrx_connect-0.1.73/matrx_connect/context/app_context.py +724 -0
  10. matrx_connect-0.1.73/matrx_connect/context/data_render_blocks.py +285 -0
  11. matrx_connect-0.1.73/matrx_connect/context/data_types.py +2114 -0
  12. matrx_connect-0.1.73/matrx_connect/context/emitter_protocol.py +101 -0
  13. matrx_connect-0.1.73/matrx_connect/context/error_buffer.py +115 -0
  14. matrx_connect-0.1.73/matrx_connect/context/events.py +801 -0
  15. matrx_connect-0.1.73/matrx_connect/context/media_block.py +489 -0
  16. matrx_connect-0.1.73/matrx_connect/context/operations.py +204 -0
  17. matrx_connect-0.1.73/matrx_connect/context/phases.py +32 -0
  18. matrx_connect-0.1.73/matrx_connect/context/provenance.py +214 -0
  19. matrx_connect-0.1.73/matrx_connect/context/render_blocks.py +143 -0
  20. matrx_connect-0.1.73/matrx_connect/context/tool_event_data.py +117 -0
  21. matrx_connect-0.1.73/matrx_connect/credentials.py +123 -0
  22. matrx_connect-0.1.73/matrx_connect/dependencies.py +118 -0
  23. matrx_connect-0.1.73/matrx_connect/emitters/__init__.py +5 -0
  24. matrx_connect-0.1.73/matrx_connect/emitters/console_emitter.py +365 -0
  25. matrx_connect-0.1.73/matrx_connect/emitters/error_guard.py +28 -0
  26. matrx_connect-0.1.73/matrx_connect/emitters/log_truncation.py +24 -0
  27. matrx_connect-0.1.73/matrx_connect/emitters/silent_emitter.py +130 -0
  28. matrx_connect-0.1.73/matrx_connect/emitters/stream_emitter.py +790 -0
  29. matrx_connect-0.1.73/matrx_connect/lane/__init__.py +45 -0
  30. matrx_connect-0.1.73/matrx_connect/lane/buckets.py +71 -0
  31. matrx_connect-0.1.73/matrx_connect/lane/contextvar.py +41 -0
  32. matrx_connect-0.1.73/matrx_connect/lane/core.py +725 -0
  33. matrx_connect-0.1.73/matrx_connect/lane/priorities.py +42 -0
  34. matrx_connect-0.1.73/matrx_connect/middleware/__init__.py +13 -0
  35. matrx_connect-0.1.73/matrx_connect/middleware/auth.py +1467 -0
  36. matrx_connect-0.1.73/matrx_connect/middleware/session_cookie.py +246 -0
  37. matrx_connect-0.1.73/matrx_connect/pytest_plugin.py +66 -0
  38. matrx_connect-0.1.73/matrx_connect/request_controls.py +102 -0
  39. matrx_connect-0.1.73/matrx_connect/request_latency.py +52 -0
  40. matrx_connect-0.1.73/matrx_connect/reservations.py +256 -0
  41. matrx_connect-0.1.73/matrx_connect/scoped_tokens.py +213 -0
  42. matrx_connect-0.1.73/matrx_connect/service_auth.py +183 -0
  43. matrx_connect-0.1.73/matrx_connect/streaming/FEATURE.md +46 -0
  44. matrx_connect-0.1.73/matrx_connect/streaming/__init__.py +25 -0
  45. matrx_connect-0.1.73/matrx_connect/streaming/error_capture.py +82 -0
  46. matrx_connect-0.1.73/matrx_connect/streaming/failure_classification.py +218 -0
  47. matrx_connect-0.1.73/matrx_connect/streaming/primitives.py +131 -0
  48. matrx_connect-0.1.73/matrx_connect/streaming/response.py +719 -0
  49. matrx_connect-0.1.73/pyproject.toml +75 -0
  50. matrx_connect-0.1.73/scripts/release.sh +5 -0
  51. matrx_connect-0.1.73/tests/__init__.py +0 -0
  52. matrx_connect-0.1.73/tests/test_auth_middleware.py +1100 -0
  53. matrx_connect-0.1.73/tests/test_auth_middleware_api_key.py +202 -0
  54. matrx_connect-0.1.73/tests/test_auth_middleware_organization_admission.py +291 -0
  55. matrx_connect-0.1.73/tests/test_auth_middleware_websocket_admission.py +200 -0
  56. matrx_connect-0.1.73/tests/test_chat_timing.py +187 -0
  57. matrx_connect-0.1.73/tests/test_citation_event.py +71 -0
  58. matrx_connect-0.1.73/tests/test_console_emitter_error_guard.py +18 -0
  59. matrx_connect-0.1.73/tests/test_credentials_contract.py +93 -0
  60. matrx_connect-0.1.73/tests/test_error_buffer.py +116 -0
  61. matrx_connect-0.1.73/tests/test_error_capture.py +138 -0
  62. matrx_connect-0.1.73/tests/test_execution_attribution.py +43 -0
  63. matrx_connect-0.1.73/tests/test_failure_classification.py +198 -0
  64. matrx_connect-0.1.73/tests/test_lane.py +343 -0
  65. matrx_connect-0.1.73/tests/test_log_truncation.py +28 -0
  66. matrx_connect-0.1.73/tests/test_media_block_no_storage_uri.py +78 -0
  67. matrx_connect-0.1.73/tests/test_organization_context_dependency.py +138 -0
  68. matrx_connect-0.1.73/tests/test_prepared_lane_finalization.py +213 -0
  69. matrx_connect-0.1.73/tests/test_provenance.py +62 -0
  70. matrx_connect-0.1.73/tests/test_provider_retry_event.py +58 -0
  71. matrx_connect-0.1.73/tests/test_scoped_tokens.py +115 -0
  72. matrx_connect-0.1.73/tests/test_service_auth_organization_admission.py +146 -0
  73. matrx_connect-0.1.73/tests/test_session_cookie.py +121 -0
  74. matrx_connect-0.1.73/tests/test_session_cookie_drift_detector.py +228 -0
  75. matrx_connect-0.1.73/tests/test_session_cookie_secret_stability.py +55 -0
  76. matrx_connect-0.1.73/tests/test_session_cookie_stable_wins.py +79 -0
  77. matrx_connect-0.1.73/tests/test_stream_emitter_logging.py +81 -0
  78. matrx_connect-0.1.73/tests/test_stream_emitter_replay.py +76 -0
  79. matrx_connect-0.1.73/tests/test_structured_output_event.py +87 -0
  80. matrx_connect-0.1.73/tests/test_system_app_context.py +122 -0
  81. matrx_connect-0.1.73/tests/test_tool_event_data.py +23 -0
@@ -0,0 +1,299 @@
1
+ *.pyc
2
+ secrets/
3
+ ignore/
4
+ temp/
5
+ logs/
6
+ # The broad `logs/` rule above is for RUNTIME log output, but it also matched
7
+ # the dashboard's SOURCE directory and silently swallowed an entire feature's
8
+ # files (only the pre-existing index.tsx stayed tracked), breaking the prod
9
+ # Docker build with "Could not resolve ./structured-tab". Re-include the source.
10
+ !apps/dashboard/src/features/logs/
11
+ !apps/dashboard/src/features/logs/**
12
+ todo
13
+ text_notes/
14
+ aidream/secrets/2.env
15
+ automation_matrix/matrix_processing/temp/*
16
+ cd
17
+ # Byte-compiled / optimized / DLL files
18
+ __pycache__/
19
+ *.py[cod]
20
+ *$py.class
21
+
22
+ # C extensions
23
+ *.so
24
+ .venv/
25
+
26
+ # Distribution / packaging
27
+ .Python
28
+ build/
29
+ develop-eggs/
30
+ dist/
31
+ downloads/
32
+ eggs/
33
+ .eggs/
34
+ lib/
35
+ lib64/
36
+ # The blanket lib/ rule above is from the standard Python .gitignore template
37
+ # and was silently swallowing TS source under the SPA `src/lib/` folders.
38
+ # Re-allow them explicitly so frontend builds don't ship without their lib layer.
39
+ !apps/dashboard/src/lib/
40
+ !apps/dashboard/src/lib/**
41
+ !apps/dashboard/src/features/crawler/lib/
42
+ !apps/dashboard/src/features/crawler/lib/**
43
+ !apps/workflow-studio/src/lib/
44
+ !apps/workflow-studio/src/lib/**
45
+ parts/
46
+ sdist/
47
+ var/
48
+ wheels/
49
+ share/python-wheels/
50
+ *.egg-info/
51
+ .installed.cfg
52
+ *.egg
53
+ MANIFEST
54
+
55
+ # PyInstaller
56
+ # Usually these files are written by a python script from a template
57
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
58
+ *.manifest
59
+ *.spec
60
+
61
+ # Installer logs
62
+ pip-log.txt
63
+ pip-delete-this-directory.txt
64
+
65
+ # Unit test / coverage reports
66
+ ai/tests/clean_response.json
67
+ ai/tests/cx_storage_response.json
68
+ ai/tests/execution_test.py
69
+ ai/tests/final_response.json
70
+ htmlcov/
71
+ .tox/
72
+ .nox/
73
+ .coverage
74
+ .coverage.*
75
+ .cache
76
+ nosetests.xml
77
+ coverage.xml
78
+ *.cover
79
+ *.py,cover
80
+ .hypothesis/
81
+ .pytest_cache/
82
+ cover/
83
+
84
+ # Translations
85
+ *.mo
86
+ *.pot
87
+
88
+ # Django stuff:
89
+ *.log
90
+ local_settings.py
91
+ db.sqlite3
92
+ db.sqlite3-journal
93
+
94
+ # Flask stuff:
95
+ instance/
96
+ .webassets-cache
97
+
98
+ # Scrapy stuff:
99
+ .scrapy
100
+
101
+ # Sphinx documentation
102
+ docs/_build/
103
+
104
+ # PyBuilder
105
+ .pybuilder/
106
+ target/
107
+
108
+ # Jupyter Notebook
109
+ .ipynb_checkpoints
110
+
111
+ # IPython
112
+ profile_default/
113
+ ipython_config.py
114
+
115
+ # pyenv
116
+ # For a library or package, you might want to ignore these files since the code is
117
+ # intended to run in multiple environments; otherwise, check them in:
118
+ # .python-version
119
+
120
+ # pipenv
121
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
122
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
123
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
124
+ # install all needed dependencies.
125
+ #Pipfile.lock
126
+
127
+ # poetry
128
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
129
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
130
+ # commonly ignored for libraries.
131
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
132
+
133
+ # pdm
134
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
135
+ #pdm.lock
136
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
137
+ # in version control.
138
+ # https://pdm.fming.dev/#use-with-ide
139
+ .pdm.toml
140
+
141
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
142
+ __pypackages__/
143
+
144
+ # Celery stuff
145
+ celerybeat-schedule
146
+ celerybeat.pid
147
+
148
+ # SageMath parsed files
149
+ *.sage.py
150
+
151
+ # Environments
152
+ .env
153
+ .env_remote
154
+ .venv
155
+ env/
156
+ venv/
157
+ ENV/
158
+ env.bak/
159
+ venv.bak/
160
+ .env.armanonly
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # random armani files
181
+ /armani_dev/secrets/
182
+ /armani/
183
+ /_armani/
184
+
185
+
186
+
187
+ # pytype static type analyzer
188
+ .pytype/
189
+
190
+ # Cython debug symbols
191
+ cython_debug/
192
+
193
+ .idea/
194
+ .vscode/
195
+ /node_modules/
196
+
197
+ # Frontend pnpm workspace (apps/) — node_modules at the workspace root and any
198
+ # member, plus Vite caches and build output. The unified lockfile (apps/pnpm-lock.yaml)
199
+ # IS committed; everything below is regenerated.
200
+ node_modules/
201
+ apps/**/.vite/
202
+ apps/**/dist/
203
+ .vite/
204
+
205
+ dump.rdb
206
+
207
+ frontend/
208
+
209
+ # AME Temp Files and directory structure
210
+ # Ignore all files in the temp directory and its subdirectories
211
+ /temp/**/*
212
+ /tmp/**/*
213
+
214
+ # Allow .gitkeep files to retain directory structure
215
+ !/temp/**/.gitkeep
216
+ !/tmp/**/.gitkeep
217
+
218
+ # Armani
219
+ .history*
220
+ .history/
221
+ /local_data/
222
+ local_reports_data/
223
+ webscraper/quick_scrapes/temp/
224
+ automation_matrix/ai_apis/fireworks/_dev/*
225
+ automation_matrix/ai_apis/fireworks/_dev/fireworks_sample.py
226
+ *.pdf
227
+ *.flac
228
+ *.mp3
229
+ *.wav
230
+ miniconda.sh
231
+ /database/python_sql/temp_data/
232
+ .history*
233
+ .history/
234
+ .history/
235
+
236
+ _dev/
237
+ /_dev/
238
+ requirements_filtered.txt
239
+
240
+ # matrx-dev-tools backups
241
+ .env-backups/
242
+ # Matrx Ship config (contains API key)
243
+ .matrx-ship.json
244
+
245
+ # Matrx config (contains API keys)
246
+ .matrx.json
247
+ .matrx-tools.conf
248
+
249
+ # Claude Code local worktrees and per-user settings
250
+ .claude/worktrees/
251
+ .worktrees/
252
+ .claude/settings.local.json
253
+
254
+ # Append-only snapshots from matrx_utils.update_history (unbounded; do not commit)
255
+ common/utils/data_in_code/data_history.json
256
+ packages/matrx-utils/matrx_utils/data_in_code/data_history.json
257
+
258
+ # Tool-dispatch debug logs — one file per server start, never committed
259
+ .matrx-debug/
260
+
261
+ # macOS Finder metadata
262
+ .DS_Store
263
+ **/.DS_Store
264
+
265
+ # Environment files
266
+ .env
267
+ .env.*
268
+ *.env
269
+ *.env.*
270
+
271
+ # Keep safe templates trackable
272
+ !.env.example
273
+ !.env.sample
274
+ !.env.template
275
+
276
+ # Never commit local OAuth client/token artifacts
277
+ credentials.json
278
+ token*.pickle
279
+ token*.json
280
+ tests_trials/rag_tests/kg_export_output/
281
+
282
+ # Pooler-contamination watch output (machine-local evidence, not source)
283
+ db/evidence/
284
+
285
+ # Pooler contamination evidence — machine-local forensic capture, not source
286
+ db/evidence/
287
+ db/mirror/.env.mirror
288
+ supabase/.temp/
289
+ **/supabase/.temp/
290
+ # Packed npm artifacts. Kept locally for a one-time bootstrap publish; never
291
+ # committed (they are large binaries regenerable with `pnpm pack`).
292
+ apps/shared/*/*.tgz
293
+
294
+ # Guard forcing-function scratch packages. These tests plant real files inside
295
+ # the real tree the guard scans (a temp dir would prove nothing) and delete
296
+ # them in teardown — ignoring them keeps the auto-commit tooling from
297
+ # capturing one mid-run.
298
+ _kind_boundary_scratch*/
299
+ _kind_marker_law_scratch*/
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,285 @@
1
+ # CLAUDE.md — matrx-connect
2
+
3
+ > **Operating Principle: Build the platform, not the artifact.** Every task is a probe that exposes a missing capability — build it, then consume it. Code that only serves one artifact is forbidden. Full doctrine: [/PRINCIPLES.md](../../PRINCIPLES.md).
4
+
5
+ **Package:** `matrx-connect` (PyPI) — Python 3.12+ — currently v0.1.70
6
+ **Role in the graph:** Tier 1. Depends only on `matrx-utils`. Used by `matrx-graph`, `matrx-scraper` (optional), and `matrx-ai`.
7
+
8
+ ---
9
+
10
+ ## Read this first
11
+
12
+ `matrx-connect` is the FastAPI-integration layer for the Matrx ecosystem: **auth middleware, streaming response infrastructure, request-scoped `AppContext`, and the `Emitter` protocol.** It is *not* a database connectivity package (despite the name — "connect" refers to connecting FastAPI apps to the Matrx streaming / auth contract).
13
+
14
+ Any aidream-specific concept that belongs in a package belongs here, **not in the packages above**. If a sibling package needs auth or streaming primitives, it should `configure_ext(...)` or import from matrx-connect; aidream should never be touched.
15
+
16
+ ---
17
+
18
+ ## What this package provides
19
+
20
+ - **`AppContext`** (`matrx_connect.context.app_context`) — request-scoped identity + metadata held in a `ContextVar`. Fields: `user_id`, `email`, `auth_type`, `is_authenticated`, `is_admin`, `request_id`, `fingerprint_id`, `api_keys`, `metadata`, `emitter`. **Frozen** (Phase 1) — derive a modified copy with `with_overrides(**kwargs)` and install it via `set_app_context(...)` (the old mutating `extend()` was removed and now raises). Persistence intent fields: `store` (False = write NOTHING, ephemeral) and `system_run` (True = internal machine call; consumers persist only the cost spine and skip transcript machinery — set via matrx-ai `run_agent(system_run=True)`, semantics documented on the field). Durable work uses the paired, validated `execution_kind` + UUID `execution_id` fields via `with_execution_attribution(...)`; persistence stamps these directly instead of scraping mutable metadata or inferring ownership from conversation IDs. Forked contexts preserve the attribution.
21
+ - **`system_app_context(feature, *, user_id, ...)`** (`matrx_connect.context.app_context`) — **the ONE way to get an ambient `AppContext` for work that runs OUTSIDE a request** (a `detached_task`, queue worker, NOTIFY listener, scheduled run). Those start with EMPTY contextvars, so anything downstream that calls `get_app_context()` — **every agent run does**, via `run_agent` → `child_agent_context` — dies with *"No AppContext is set. Ensure AuthMiddleware is registered…"*, an error that blames middleware for a request that never existed. **Idempotent**: yields the existing context untouched when one is ambient, so it is safe to wrap an entry point that has both an HTTP and a background caller. Non-persisting by default (`store=False`, `is_internal_agent=True`); pass any AppContext field as a kwarg to override (`system_run=True` to keep the cost spine). **Install it at the FUNNEL every background dispatcher passes through, not at each agent call site** — a guard placed one layer too deep is exactly how aidream's PDF page cleaner silently embedded raw text for every PDF (2026-07-11) while the NER stage below it worked fine. Never hand-roll `AppContext(...)` + `set_app_context` + `finally: clear_app_context` again — that copy-paste block is what this replaced.
22
+ - **Auth middleware** (`matrx_connect.middleware.auth`) — pluggable Supabase JWT / fingerprint resolution. Constructor takes JWT verification settings plus injected `is_admin_resolver`, `resolve_guest`, and optional organization resolver callbacks; static admin tokens are forbidden.
23
+ - **`Emitter` protocol + `StreamEmitter` / `ConsoleEmitter`** — async event emitters with the full vocabulary (`send_chunk`, `send_reasoning`, `send_data`, `send_phase`, tool events, `fatal_error`, `send_end`).
24
+ - **`buffer_error_events()`** — task-local emitter overlay for retry ownership boundaries. Non-error events pass through immediately; ERROR events replay only when the scoped operation returns normally and are discarded when it raises. Stream/console emitters independently enforce at-most-one terminal ERROR per lifecycle.
25
+ - **`create_streaming_response(...)`** (`matrx_connect.streaming.response`) — the canonical streaming response wrapper. Spawns the task, manages lifecycle, emits heartbeats, clears the ContextVar on exit. Opens every stream with `phase="connected"` followed by the route's `initial_message` as an INFO event (`code="initial_message"`, mirrored into `system_message`/`user_message`) — the client's opening progress line. (Before 2026-08-10 the parameter was accepted and silently dropped.)
26
+ Detached `StreamEmitter`s keep publishing after the first HTTP reader leaves, retain every sequenced NDJSON frame for the task's live lifetime, and expose `generate_replay()` for owner-authorized rejoin routes. `register_active_stream(request_id, emitter)` / `get_active_stream` / identity-guarded `unregister_active_stream` are the ONE process-local delivery registry; durable cross-process status/result recovery stays in the host runtime spine.
27
+ Classified exceptions may attach `error_info.code` and `error_info.details`; the wrapper
28
+ forwards both and adds the current request ID to details so clients retain the full
29
+ diagnostic contract instead of receiving only a generic label.
30
+ - **Failure capture** (`matrx_connect.streaming.error_capture`) — the injected host sink for
31
+ durable `system_error` records. Pytest processes are denied by default; local fake sinks must
32
+ opt in explicitly. Contract: [`matrx_connect/streaming/FEATURE.md`](matrx_connect/streaming/FEATURE.md).
33
+ - **`context_dep`** — FastAPI `Depends()` helper for pulling `AppContext` into a route handler.
34
+ - **`require_organization_context`** — route dependency that declares required `X-Organization-Id`, validates its UUID, and rejects unresolved or mismatched middleware context before handler code. Admin callers use the same target-org contract; the returned `AppContext` preserves their admin tier.
35
+ - **Event data types** (`matrx_connect.context.events`, `.data_types`, `.operations`, `.tool_event_data`, `.render_blocks`) — Pydantic event payload schemas shared with the frontend.
36
+
37
+ Public exports from `matrx_connect/__init__.py`: `AppContext`, `Emitter`, `EventType`, `StreamEvent`, `StreamEmitter`, `ConsoleEmitter`, `BufferedErrorEmitter`, `BufferedErrorEvent`, `buffer_error_events`, `context_dep`, `require_organization_context`, `get_app_context`, `set_app_context`, `try_get_app_context`, `clear_app_context`, `system_app_context`.
38
+
39
+ ---
40
+
41
+ ## The streaming endpoint pattern (canonical)
42
+
43
+ Every streaming route in aidream and in any external consumer is expected to follow this shape. Do not deviate inside this package:
44
+
45
+ ```python
46
+ from matrx_connect import AppContext, context_dep
47
+ from matrx_connect.streaming import create_streaming_response
48
+ from fastapi import Depends
49
+
50
+ @router.post("/thing")
51
+ async def do_thing(payload: ThingRequest, ctx: AppContext = Depends(context_dep)):
52
+ return create_streaming_response(
53
+ ctx, _run, payload,
54
+ initial_message="Starting…", debug_label="ThingFlow",
55
+ )
56
+
57
+ async def _run(emitter, payload: ThingRequest):
58
+ # AppContext is already in the ContextVar; use get_app_context() if you need it.
59
+ result = await do_work(payload)
60
+ await emitter.send_data(ResultPayload(...).model_dump())
61
+ await emitter.send_end()
62
+ ```
63
+
64
+ Rules (enforce these on contributions to matrx-connect and consumers):
65
+
66
+ - `create_streaming_response` owns the context **teardown** — it `clear_app_context`s on exit; never `clear_app_context` yourself. A route that needs a route-scoped field (e.g. `conversation_id`) for the streaming task installs it with `ctx = ctx.with_overrides(field=value); set_app_context(ctx)` BEFORE calling `create_streaming_response`. AppContext is **frozen** (Phase 1), so `set_app_context` is REQUIRED to install the derived ctx into the ContextVar — omit it and the override is silently dropped.
67
+ - The task function takes `emitter` as its first arg, then business args. It does not handle `CancelledError` or generic exceptions; infrastructure does.
68
+ - Heartbeats and client-disconnect cleanup are the middleware's job, not the task's.
69
+ - **Detach preserves delivery, not only work.** Closing the primary generator must never set `StreamEmitter.cancelled` when `detach_on_disconnect=True`; doing so makes every later `send_*` a no-op and leaves rejoining clients with lifecycle-only status.
70
+ - **`debug_label` is a LOG label, never an error identity.** It carries per-run ids
71
+ (`VisionInterview:72175649`), so the crash path derives the emitted `error_type`
72
+ through `stable_error_type()`, which strips them. Emitting
73
+ `f"{debug_label.lower()}_error"` mints a NEW error type for every session —
74
+ ungroupable, uncountable, unalertable, and the Error Inspector shows
75
+ `operation: unknown`.
76
+
77
+ ### The crash path never blames the user, and never ships a terminal banner
78
+
79
+ [`failure_classification.py`](matrx_connect/streaming/failure_classification.py) sits on
80
+ the ONE path every stream crash takes. Three rules, each earned on 2026-08-17 when
81
+ `agent.slot_definition` was renamed to `agent.mandate` under a running build:
82
+
83
+ 1. **A recognized infrastructure failure gets a stable type and an honest message.**
84
+ `database_schema_mismatch` / `database_unavailable` / `database_permission_denied`,
85
+ each saying *"nothing you did caused it and your work is saved"*. The old generic
86
+ text — *"Please try again or adjust your settings"* — told a non-technical user to
87
+ go fix settings when the server could not reach a renamed table.
88
+ 2. **Classification is by exception CLASS NAME.** matrx-connect must not depend on
89
+ matrx-orm or asyncpg to recognize a database error; the whole `__mro__` and
90
+ `__cause__`/`__context__` chain is walked so a service that wraps the DB error in
91
+ its own type still classifies.
92
+ 3. **Everything client-bound is ANSI-stripped.** Operator banners are colour-escaped;
93
+ a browser renders `` literally. The raw text is preserved in the durable
94
+ `system_error` record — strip at the wire, never at the sink.
95
+
96
+ An ordinary application bug stays unclassified on purpose: only infrastructure earns
97
+ "not your fault", or a real bug gets laundered into "try again in a moment".
98
+
99
+ ---
100
+
101
+ ## The auth contract (read this if you're touching `middleware/auth.py`)
102
+
103
+ `AuthMiddleware` is the only place `ctx.user_id` gets set. The contract is intentionally narrow:
104
+
105
+ - **Accepted token types:** Supabase-signed JWTs, plus — when the host injects `resolve_api_key` — `mx_`-prefixed AI Matrx API keys (see the API-key lane below). Every other bearer falls through. For JWTs the middleware verifies the signature with `jwt_secret`/JWKS, checks `aud="authenticated"`, and pulls the user_id from `sub`.
106
+ - **The API-key lane (ratified C16, 2026-08-29).** ONE `Authorization: Bearer` header for every credential kind, disambiguated by SHAPE: three-dot JWTs go to the one verifier, `mx_`-prefixed bearers go to the host-injected `resolve_api_key` callback (`ApiKeyResolver → ApiKeyIdentity | None`). The HOST owns the lookup, hashing, constant-time compare, and caching — the middleware never sees a secret store. Success sets `auth_type="api_key"`, `user_id` = the key's REAL service-identity `auth.users` row, and admits the key's ONE bound organization as the request org (`X-Organization-Id` present must EQUAL it → 409 `organization_context_mismatch`; absent → the key's org is admitted). Rejection (None or a raising resolver) leaves the request anonymous with compact INFO logging — never a 500, never token material in logs. Nothing downstream branches on "is this an API key". Design: `common-docs/projects/npm-package-extraction/API-KEY-LANE-DESIGN.md`.
107
+ - **Preserve verified JWT lifecycle metadata.** `VerifiedToken.expires_at` and `.issuer` survive crypto validation, single-flight sharing, and cache hits so OAuth resource-server adapters can populate their native access-token contract without decoding the bearer a second time.
108
+ - **A removed signing key stays removed.** An unknown `kid` forces at most one bounded JWKS refresh; if the refreshed document still omits it, reject the token. Supabase operators must keep a rotated key in `previously used` until every token it signed has expired—never weaken verification to compensate for premature revocation.
109
+ - **An unknown JWKS `kid` is a denied credential, not an application failure.** Log the rejection at WARNING so it remains correlatable without entering the ERROR-only repair queue; do not create `system_error` for expected stale, foreign, or forged tokens.
110
+ - **An HS256 token at an asymmetric-only verifier is also a denied credential.** Reject it at WARNING when `jwt_secret` is absent; it must not enter the ERROR-only repair queue or create `system_error`.
111
+ - **An authenticated request with no admitted organization is expected caller denial.** Return `organization_required` and log `[AUTH][REJECT]` at WARNING; deliberate HTTP 400 and WebSocket 4400 control flow never enters the ERROR-only repair queue or creates `system_error`.
112
+ - **The file-session cookie lane has a DRIFT DETECTOR (2026-08-30 outage class).** `mx_files_session` cookies are HMAC-signed with `guest_fingerprint_secret`; with per-process random fallback secrets across containers, cookies mint on one instance and fail on another — every private `<img>` 401s silently. The middleware now classifies cookie failures (`session_cookie.classify_session_cookie_value`): a PRESENT, structurally valid, UNEXPIRED cookie failing HMAC logs CRITICAL with the machine-greppable token `MX_COOKIE_SIGNATURE_MISMATCH` (rate-limited per burst window, `COOKIE_DRIFT_LOG_WINDOW_SECONDS`); expired/malformed stay silent-anonymous. `AuthMiddleware.secret_is_ephemeral` + `session_cookie.get_session_cookie_secret_source()` expose whether the process fell back to a random secret — matrx-files' `POST /files/session` mint response carries it as `"secret_source": "stable"|"ephemeral"` so clients can scream. Hosts MUST inject one stable secret on every instance.
113
+ - **No static admin token, no env-mapped user_id.** The legacy `admin_token` + `admin_user_id` escape hatch was removed (intentionally — see [`SCRAPER_SECURITY_BUGS.md`](../../docs/archive/2026/scraper__SCRAPER_SECURITY_BUGS.md) for why). Do not re-add it.
114
+ - **`is_admin` resolution:** an injected `is_admin_resolver(user_id) → bool` consults the host's `public.admins` table on every authenticated request. The middleware itself never decides admin status.
115
+ - **Local dev / agent authentication:** the host's dev-login endpoint mints a real Supabase JWT for a real user_id (gated by an env flag never set in prod). Agents use that JWT like any other client. There is no agent-specific code path in this package, and there must never be one.
116
+
117
+ If you find yourself wanting to add a fast path that maps a token-or-secret to a user_id without going through JWT validation, stop. The two sanctioned non-JWT lanes are exactly the injected ones above — guest fingerprints and the C16 API-key lane, both resolving to REAL `auth.users` identities via host callbacks. Anything else belongs in the host as an explicitly-named dev-only endpoint that *produces* a real JWT.
118
+
119
+ ### Server-to-server: the approved-server handshake (`service_auth.py`) — NOT the middleware
120
+
121
+ The rule above is about the **middleware**. There is a separate, sanctioned way for one of our **servers** (not a person) to call a protected route: `matrx_connect.service_auth`. This is **opt-in per route** — a host applies it explicitly to specific routers; it is never wired into `AuthMiddleware`, and the middleware stays JWT-only.
122
+
123
+ - `require_authenticated_or_service(secret, *, require_user=False)` — a router dependency that admits EITHER a real user login (`is_authenticated` already set) OR an approved-server call: the bearer equals the shared `secret` (constant-time), and the acting user is named by the `X-Matrx-User-Id` header. It stamps that user into the request context.
124
+ - `verify_approved_server(request, *, secret, secret_header="authorization", require_user=False)` — the pure check (used by callers with a custom header or their own 503-when-unconfigured signal, e.g. the sandbox bridge / media-healer).
125
+
126
+ **Why this is safe and NOT the removed escape hatch:** the old hatch hard-bound every call to ONE fixed `admin_user_id` env var, so writes were mis-attributed to a fake admin and users' data mixed (the incident in [`SCRAPER_SECURITY_BUGS.md`](../../docs/archive/2026/scraper__SCRAPER_SECURITY_BUGS.md) §D). This primitive **never defaults to a user** — the acting user is exactly what the caller names, or empty for ephemeral no-write calls; a write endpoint enforces its own owner and raises on a missing user. It's the reusable form of the pattern the sandbox bridge (`cloud_files_bridge`) already used; the scraper, sandbox bridge, and media-healer now share this one primitive. Adding a per-route service path here is allowed; adding a token→user path to the middleware is not.
127
+
128
+ ---
129
+
130
+ ## The configuration / injection pattern
131
+
132
+ matrx-connect is itself a "thing other packages and apps inject into". Its own injection surface:
133
+
134
+ - `AuthMiddleware(...)` constructor — pass `jwt_secret`, `is_admin_resolver`, and `resolve_guest` callback from the host. (`admin_token` / `admin_user_id` are no longer accepted — see "The auth contract" above.)
135
+ - `AppContext.with_overrides(**kwargs)` + `set_app_context(...)` — host apps install request-scoped overrides (e.g. `conversation_id`) before the streaming task runs. AppContext is frozen; `extend()` was removed in Phase 1 and now raises.
136
+ - `Emitter` is a `typing.Protocol` — any object that satisfies the shape works; `StreamEmitter` is just the default HTTP/JSONL implementation.
137
+
138
+ Inside this package:
139
+
140
+ - **Never** read environment variables at import time. The middleware takes its config as constructor args.
141
+ - **Never** assume a specific database or ORM. The only persistence matrx-connect does is NDJSON over HTTP.
142
+
143
+ ---
144
+
145
+ ## Dependency rules specific to this package
146
+
147
+ - ✅ May import `matrx_utils` (currently only for `vcprint` dev-logging).
148
+ - ❌ No `from matrx_orm import …`
149
+ - ❌ No `from matrx_graph import …`
150
+ - ❌ No `from matrx_scraper import …`
151
+ - ❌ No `from matrx_ai import …`
152
+ - ❌ No `from aidream import …`, no root-module imports.
153
+
154
+ If a feature seems to need an ORM (e.g. auth wants to load users from a DB), inject the loader function — don't pull in matrx-orm.
155
+
156
+ ---
157
+
158
+ ## The event vocabulary — when to use which
159
+
160
+ `matrx_connect.context.events.EventType` is THE registry for everything Python emits to the client. Every event has a Pydantic payload class registered in `PAYLOAD_REGISTRY`; `build_event(event_type, payload)` is the only sanctioned constructor (rejects payload-class mismatches at emit time). Adding a new event type is a coordinated change: enum entry + payload class + registry entry + Emitter Protocol method + StreamEmitter/ConsoleEmitter implementations + (optional) re-exports in `context/__init__.py`.
161
+
162
+ The most-confused choice points — read these before you reach for a new emitter call:
163
+
164
+ | You want to tell the client… | Use | Don't use |
165
+ |---|---|---|
166
+ | Token-by-token text or reasoning TEXT | `send_chunk` / `send_reasoning_chunk` | DATA |
167
+ | **The model started / stopped THINKING (content-less)** | **`send_reasoning_state`** | `send_phase`, `send_chunk` |
168
+ | Lifecycle stage (e.g. "connected", "tool_loop_started") | `send_phase` | INFO |
169
+ | Structured payload the UI renders inline (search results, conversation IDs, image URLs, …) | `send_data` | DATA + secret intent encoding |
170
+ | Operational note that decorates a successful response | `send_warning` (with severity) | ERROR |
171
+ | The response itself failed before/during stream | `send_error` (then `send_end`) | WARNING |
172
+ | Tool started / progress / completed | `send_tool_event` | INFO |
173
+ | A matrx-orm record was reserved / updated | `send_record_reserved` / `send_record_update` | RESOURCE_CHANGED |
174
+ | **A non-orm resource changed; client should refetch if displaying it** | **`send_resource_changed`** | DATA, INFO |
175
+ | **An agent finished and its declared `output_schema` was applied to the response** | **`send_structured_output`** | DATA, RECORD_UPDATE |
176
+ | **A provider attached a citation to the streaming answer** | **`send_citation`** | DATA, INFO |
177
+
178
+ ### `REASONING` — content-less thinking lifecycle (added 2026-07-05)
179
+
180
+ `send_reasoning_state("started" | "stopped")` — the model opened or closed a thinking/reasoning block, **independent of whether reasoning text streams**. Every provider signals a reasoning block start even when the reasoning text is suppressed (Anthropic `display="omitted"`, OpenAI `reasoning_summary="never"`, Gemini `include_thoughts=False`) — this event carries that signal so the UI shows a "thinking" state instead of a silent heartbeat gap.
181
+
182
+ - **This is NOT `reasoning_chunk`.** `reasoning_chunk` carries reasoning TEXT (only when requested); `reasoning` is the on/off lifecycle and fires regardless.
183
+ - **This is NOT a `phase`.** A model can think while the overall phase is `generating` or `using_tools`; reasoning is a sub-state that pairs `started`/`stopped`, not a one-way progress stage.
184
+ - **Emitted by the provider stream parsers**, not the orchestrator — each provider fires it at its own reasoning-block boundary (`matrx_ai/providers/*/*_api.py`). Balanced per turn; a turn with no thinking emits nothing.
185
+
186
+ ### `CITATION` — live citation stream (added 2026-07-17)
187
+
188
+ `send_citation(CitationPayload(block_index, citation))` — one **normalized** citation (the canonical cross-provider `NormalizedCitation` shape owned by `matrx_ai.config.citations`; matrx-connect carries it as a dict) attached to the streaming answer. Emitted live by the provider stream parsers (Anthropic `citations_delta`, OpenAI `response.output_text.annotation.added`) or at stream settle for terminal-only providers (Gemini grounding). The same citations also persist on the text part's top-level `citations` (`TextPart.citations`) — the stream event is the live channel, storage is the settle channel; both carry the identical shape.
189
+
190
+ ### `RESOURCE_CHANGED` — generic refresh hint (added 2026-04-27)
191
+
192
+ Use `send_resource_changed(*, kind, action, resource_id, sandbox_id?, user_id?, metadata?)` whenever your code mutates something on the server side that a client may be displaying or caching, and **no other event type already covers it**. This is the generic "refetch this" primitive — distinct from RECORD_UPDATE (matrx-orm-row-specific) and DATA (which carries actual content, not change hints).
193
+
194
+ `kind` is namespaced and intentionally open-ended; consumers register their own. Established conventions:
195
+
196
+ - `fs.file`, `fs.directory` — sandbox / agent workspace filesystem (emitted by matrx-ai's `fs_write`, `fs_patch`, `fs_mkdir` — see `matrx_ai/tools/_change_events.py`)
197
+ - `cld_files` — AI Dream cloud-files row changed (size, version, mime_type)
198
+ - `sandbox.cwd` — agent's working directory shifted
199
+ - `cache.<key>` — explicit cache invalidation hint
200
+ - `active_tools` — the agent's active tool set changed mid-loop. Emitted by matrx-ai's `tools/dynamic_drain.py` whenever a registered tool calls `ctx.queue_tool_changes(...)` and the orchestrator drains it between iterations. **Exactly one event per non-empty drain**; an empty drain (no pending mutations) emits nothing — the channel is silent when the active set is unchanged. `metadata` carries:
201
+ - `added_tools: list[str]` — names of tools just added to the active set (in queue order; possibly empty when this drain only removed).
202
+ - `removed_tools: list[str]` — names of tools just removed (deduped, order preserved).
203
+ - `active_count: int` — total tools active *after* the drain (`config.tools` + `config.custom_tools`); use this to keep client-side counters in sync without reconciling lists.
204
+ - `added: int`, `removed: int`, `sources: list[str]` — back-compat scalars from the original payload; safe to ignore in new consumers.
205
+
206
+ The FE re-renders any "active tools" UI on this signal — see [cx_chat__TOOL_INJECTION_REFACTOR.md](../../docs/archive/2026/cx_chat__TOOL_INJECTION_REFACTOR.md) (archived) for the full pattern. The matrx-extend Chrome extension consumes this event into its `useActiveToolsStore` to update the Tools-tab badge and `loaded_categories` hint without polling.
207
+
208
+ `action` is a closed `Literal`: `created` | `modified` | `deleted` | `moved` | `renamed` | `invalidated`. Use `invalidated` as the batch/coalesced "I changed many things under this scope" hint (a directory wipe, a `git checkout` that touches dozens of files) instead of fanning out to one event per file.
209
+
210
+ For move/rename, `resource_id` is the **new** identifier and `metadata.previous_id` carries the old one.
211
+
212
+ The wire shape is auto-emitted into `aidream/api/generated/stream-events.ts` via the `/schema/all` endpoint, so the FE gets typed `ResourceChangedPayload` after running `pnpm sync-types`.
213
+
214
+ When in doubt: there is **no shame in adding a more specific event type later** if `kind` strings start to feel like a sub-type system. The point of RESOURCE_CHANGED is to give every package a typed home for invalidation hints without forcing matrx-connect to know about every domain.
215
+
216
+ ### `STRUCTURED_OUTPUT` — agent's parsed response (added 2026-05-12)
217
+
218
+ Emitted automatically by `matrx_ai.agents.Agent.execute` whenever the agent has an `output_schema` declared and the execution loop has produced its final assistant message. Carries the full JSON Schema AND the parsed payload, so the FE can drive generic renderers (tables, forms, type-checked accessors) without re-running extraction or re-fetching the agent record.
219
+
220
+ - Fires AFTER all `CHUNK` events — prose-first streaming behavior is preserved; the structured event is a capstone, not a replacement.
221
+ - Fires whether extraction succeeded or failed. `success: false` (with `data: null` and a human-readable `reason`) tells the FE the agent declared a contract and the parser couldn't honour it — surface the gap, don't silently drop.
222
+ - Fires once per agent completion. Sub-agents emit their own events, tagged with the matching `operation_id` so the FE can attribute them to the right sub_agent init/completion pair.
223
+ - The schema is in the payload (verbatim from `agent.output_schema["schema"]`) — clients should not have to look up the agent to know the contract.
224
+ - Kind-bound executions also carry `kind`, `kind_version`, `kind_checked`, and
225
+ `kind_errors`. Treat `kind_checked=false` as skipped/unverified, never as pass.
226
+
227
+ Wire shape:
228
+
229
+ ```json
230
+ {
231
+ "event": "structured_output",
232
+ "data": {
233
+ "schema_name": "combined_sdt_index",
234
+ "json_schema": { "type": "object", "required": ["items"], "properties": { … } },
235
+ "data": { "items": [ … ] },
236
+ "success": true,
237
+ "reason": "",
238
+ "match_count": 1,
239
+ "agent_name": "WC Medical & Legal Report Extractor",
240
+ "operation_id": null,
241
+ "kind": "agent_io_7f3a_output_ab12cd34",
242
+ "kind_version": 1,
243
+ "kind_checked": true,
244
+ "kind_errors": []
245
+ }
246
+ }
247
+ ```
248
+
249
+ The FE picks up the typed `StructuredOutputPayload` automatically after running `pnpm sync-types`.
250
+
251
+ **Do not** hand-roll a `send_data` event to ship structured agent output. The funnel is `Agent.output_schema` + automatic emission. If a caller needs the parsed value server-side without an agent (e.g. a raw `execute_ai_request` call), call `parse_agent_output(text, schema)` directly — but the standard agent path emits the event for you.
252
+
253
+ ---
254
+
255
+ ## Python standards (same as root)
256
+
257
+ - Full type hints on every signature. `Emitter` / `AppContext` shapes are part of the public contract; changing them is a breaking change.
258
+ - No docstrings unless the symbol is user-facing (the ones in `context/`, `streaming/`, `middleware/` qualify — keep them one line).
259
+ - Explicit exception handling. `fatal_error` is the one-way door for unrecoverable stream errors.
260
+
261
+ ---
262
+
263
+ ## Testing this package in isolation
264
+
265
+ ```bash
266
+ uv run pytest packages/matrx-connect/tests
267
+ ```
268
+
269
+ Tests must run without any sibling package beyond `matrx-utils`. Use FastAPI's `TestClient` for middleware; mock `resolve_guest` rather than reaching into aidream.
270
+
271
+ ### This package ships the ambient-AppContext leak guard for the WHOLE monorepo
272
+
273
+ `matrx_connect/pytest_plugin.py` holds the autouse `_no_leaked_app_context` fixture — Layer 2 of the ambient-AppContext contract, which FAILS by name any test that leaves `_app_context` set (Layer 1 is each test cleaning up after itself). It lives here because this package owns that process-global ContextVar.
274
+
275
+ It is registered as a **`pytest11` entry point**, not a conftest, and that is load-bearing: matrx-ai, matrx-assignment, matrx-files, matrx-orm, matrx-runtime and matrx-seo each declare their own `[tool.pytest.ini_options]`, so `pytest packages/<pkg>/tests` makes that package the rootdir and the repo-root `conftest.py` is never loaded — the guard used to not run at all in a standalone package run, which is how a real leak in matrx-ai survived until it happened to be collected alongside matrx-connect. An entry-point plugin is auto-loaded from installed distribution metadata whatever the rootdir, so it reaches even the packages that (correctly) do not depend on matrx-connect, with no conftest to copy-paste and no new sibling dependency anywhere.
276
+
277
+ - **ONE definition — edit it here.** Never copy the fixture into a conftest; copies drift and that drift is the bug this replaced.
278
+ - Standalone independence is untouched: where matrx-connect isn't installed, neither is the ContextVar, so there is nothing to guard.
279
+ - Adding the entry point does not change any consumer's version floor (nothing imports the module). Opt out of a run with `-p no:matrx_app_context_guard`.
280
+
281
+ ---
282
+
283
+ ## Known issues
284
+
285
+ Tracked centrally in the root `PACKAGES_MIGRATION_PLAN.md`. This package is the cleanest of the bunch — no known import violations or hardcoded paths.