flightdeck-sensor 0.5.2__tar.gz → 0.5.3__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.
- flightdeck_sensor-0.5.3/CLAUDE.md +78 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/PKG-INFO +4 -2
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/__init__.py +46 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/config.py +4 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/session.py +5 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/types.py +98 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/pyproject.toml +7 -1
- flightdeck_sensor-0.5.3/tests/unit/test_egress_url_validation.py +235 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_session.py +8 -1
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_session_lifecycle_enrichment.py +48 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/.gitignore +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/Makefile +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/README.md +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/compat/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/compat/crewai_mcp.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/agent_id.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/context.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/errors.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/exceptions.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/mcp_policy.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/policy.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/core/schemas.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/anthropic.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/base.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/crewai.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/langgraph.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/litellm.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/mcp.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/mcp_identity.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/openai.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/provider.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/anthropic.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/litellm.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/openai.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/protocol.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/py.typed +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/transport/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/transport/client.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/transport/retry.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/conftest.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/__init__.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_agent_id.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_agent_type_validation.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_compat_crewai_mcp.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_context.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_cross_agent_messages.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_custom_directives.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_d150_tool_capture_destination.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_errors.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_framework_attribution.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_interceptor.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_interceptor_crewai.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_interceptor_langgraph.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_llm_event_enrichment.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_identity.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_interceptor.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_policy_emission_enriched.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_policy_enforcement.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_policy_enforcement_all_paths.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_originating_event_id.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_patch.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy_decision_summary.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy_emission_enriched.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy_events.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_prompt_capture.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_provider_enum.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_providers.py +0 -0
- {flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_transport.py +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Flightdeck Sensor -- Claude Code Rules
|
|
2
|
+
|
|
3
|
+
> Component-specific rules moved out of the root CLAUDE.md so they load only
|
|
4
|
+
> when working under sensor/. All root CLAUDE.md rules still apply; the
|
|
5
|
+
> numbering below keeps the original rule numbers.
|
|
6
|
+
|
|
7
|
+
## Sensor Rules
|
|
8
|
+
|
|
9
|
+
27. **The sensor must never add meaningful latency to the agent's hot path.**
|
|
10
|
+
All control plane communication is fire-and-forget or background.
|
|
11
|
+
Never introduce synchronous blocking calls in the LLM call intercept path.
|
|
12
|
+
|
|
13
|
+
28. **The sensor must fail open.** If the control plane is unreachable and
|
|
14
|
+
FLIGHTDECK_UNAVAILABLE_POLICY=continue, the agent proceeds with no enforcement.
|
|
15
|
+
Do not raise exceptions for connectivity failures.
|
|
16
|
+
|
|
17
|
+
29. **Token counting carries over from tokencap.** Do not rewrite the counting logic.
|
|
18
|
+
Extend it. Pre-call estimation, post-call reconciliation, delta correction -- these
|
|
19
|
+
are proven and must not be changed without Supervisor approval.
|
|
20
|
+
|
|
21
|
+
30. **capture_prompts defaults to False.** The default init() call never captures
|
|
22
|
+
content. Always verify this default has not been accidentally changed.
|
|
23
|
+
|
|
24
|
+
31. **init() limit param fires WARN only.** Never upgrade a local limit to BLOCK
|
|
25
|
+
or DEGRADE regardless of what the server policy says. See DECISIONS.md D035.
|
|
26
|
+
|
|
27
|
+
32. **The sensor is a library wrapper, not an OS agent.** Never add background
|
|
28
|
+
threads, polling loops, or daemon threads to the sensor beyond the existing
|
|
29
|
+
event queue drain thread. If a feature requires background activity
|
|
30
|
+
independent of LLM calls, it does not belong in the sensor.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Framework Coverage Discipline
|
|
35
|
+
|
|
36
|
+
40d. **Framework coverage discipline.** Any phase that adds
|
|
37
|
+
framework support OR changes framework-emission behaviour
|
|
38
|
+
MUST include BOTH:
|
|
39
|
+
|
|
40
|
+
1. **Real-provider playground demos** per affected framework --
|
|
41
|
+
manual, NOT in CI (they cost money and need live API
|
|
42
|
+
credentials). Live under ``playground/`` and self-skip
|
|
43
|
+
(exit 2) when the relevant API key / framework / optional
|
|
44
|
+
gateway URL is missing so ``make playground-all`` runs
|
|
45
|
+
cleanly on any box. Driven via ``make playground-<script>``
|
|
46
|
+
targets. Each demo asserts payload shape inline using
|
|
47
|
+
``print_result`` + ``raise AssertionError``; ``run_all.py``
|
|
48
|
+
exits 0 only when every script returned 0 (PASS) or 2
|
|
49
|
+
(SKIP). Results documented in the phase's audit doc before
|
|
50
|
+
PR merge.
|
|
51
|
+
2. **Integration tests** per framework × behaviour combo,
|
|
52
|
+
mock-free (or lightly mocked at the network boundary),
|
|
53
|
+
running in CI via the existing Integration job. Seed a
|
|
54
|
+
realistic event payload for each new framework + behaviour
|
|
55
|
+
combination and verify end-to-end landing.
|
|
56
|
+
|
|
57
|
+
V-pass for such a phase MUST enumerate the playground demos
|
|
58
|
+
and integration tests that will be added before
|
|
59
|
+
implementation starts. Skipping either is a phase-gate
|
|
60
|
+
failure.
|
|
61
|
+
|
|
62
|
+
Why: Phase 4 (agent communication coverage hardening) shipped
|
|
63
|
+
embeddings, streaming semantics, structured error events, and
|
|
64
|
+
session-lifecycle edge-case fixes. Mock-only coverage would
|
|
65
|
+
have let a future SDK upgrade silently break the classifier
|
|
66
|
+
(anthropic renames ``RateLimitError`` to ``QuotaError`` and
|
|
67
|
+
our classifier falls through to ``other``; no CI gate catches
|
|
68
|
+
it). The playground matrix is the only thing that exercises
|
|
69
|
+
the real class hierarchy every provider ships.
|
|
70
|
+
|
|
71
|
+
Applies to: every phase from Phase 4 onwards that touches
|
|
72
|
+
``sensor/flightdeck_sensor/interceptor/*``, adds a new
|
|
73
|
+
interceptor file, or changes the event-emission shape for an
|
|
74
|
+
existing framework.
|
|
75
|
+
|
|
76
|
+
Does NOT apply to: non-framework sensor work (transport,
|
|
77
|
+
policy, directives) — those are covered by the standard unit
|
|
78
|
+
+ integration suites.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: flightdeck-sensor
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: In-process agent observability sensor for Flightdeck
|
|
5
|
+
Project-URL: Repository, https://github.com/flightdeckhq/flightdeck
|
|
6
|
+
Author-email: Omri Arnon <omri.arnon@gmail.com>
|
|
5
7
|
License-Expression: Apache-2.0
|
|
6
8
|
Classifier: Development Status :: 3 - Alpha
|
|
7
9
|
Classifier: Intended Audience :: Developers
|
|
@@ -235,6 +235,7 @@ def init(
|
|
|
235
235
|
agent_name: str | None = None,
|
|
236
236
|
langgraph_agent_node_pattern: str | None = None,
|
|
237
237
|
mcp_block_on_uncertainty: bool = False,
|
|
238
|
+
allow_insecure_transport: bool = False,
|
|
238
239
|
) -> None:
|
|
239
240
|
"""Initialize the sensor and start the run.
|
|
240
241
|
|
|
@@ -298,6 +299,16 @@ def init(
|
|
|
298
299
|
for k8s pod grouping)
|
|
299
300
|
- ``FLIGHTDECK_UNAVAILABLE_POLICY`` -- ``"continue"`` or ``"halt"``
|
|
300
301
|
- ``FLIGHTDECK_CAPTURE_PROMPTS`` -- ``"true"`` to enable
|
|
302
|
+
- ``FLIGHTDECK_ALLOW_INSECURE_TRANSPORT`` -- ``"true"`` to allow plain
|
|
303
|
+
``http://`` egress to a non-local control plane (overrides
|
|
304
|
+
*allow_insecure_transport*)
|
|
305
|
+
|
|
306
|
+
``allow_insecure_transport`` opts in to plaintext ``http://`` egress
|
|
307
|
+
for a **non-local** control-plane host. Off by default: the resolved
|
|
308
|
+
``server`` / ``api_url`` must use ``https://`` for any public host, or
|
|
309
|
+
init raises. Local / loopback / private hosts (``localhost``,
|
|
310
|
+
``127.0.0.1``, RFC1918, ...) are always allowed over ``http``. A URL
|
|
311
|
+
with any scheme other than ``http`` / ``https`` is rejected outright.
|
|
301
312
|
|
|
302
313
|
``langgraph_agent_node_pattern`` (D126) is an optional regex
|
|
303
314
|
string that narrows which LangGraph nodes the sub-agent
|
|
@@ -316,6 +327,23 @@ def init(
|
|
|
316
327
|
_log.warning("flightdeck_sensor.init() called twice; ignoring")
|
|
317
328
|
return
|
|
318
329
|
|
|
330
|
+
# H-01: env-var precedence is preserved (env wins over the kwarg,
|
|
331
|
+
# matching every other init() knob), but a silent override of an
|
|
332
|
+
# explicitly-passed value is the vulnerability — a stale/hostile
|
|
333
|
+
# FLIGHTDECK_SERVER in the environment could redirect egress with
|
|
334
|
+
# no signal. Warn (naming both values) when the environment
|
|
335
|
+
# overrides a non-empty caller-supplied server with a different
|
|
336
|
+
# value, then keep the env value.
|
|
337
|
+
env_server = os.environ.get(_env.ENV_SERVER)
|
|
338
|
+
if env_server is not None and server and env_server != server:
|
|
339
|
+
_log.warning(
|
|
340
|
+
"%s=%r overrides the server=%r passed to init(); using the "
|
|
341
|
+
"environment value. Unset %s if this was not intended.",
|
|
342
|
+
_env.ENV_SERVER,
|
|
343
|
+
env_server,
|
|
344
|
+
server,
|
|
345
|
+
_env.ENV_SERVER,
|
|
346
|
+
)
|
|
319
347
|
resolved_server = os.environ.get(_env.ENV_SERVER, server)
|
|
320
348
|
resolved_token = os.environ.get(_env.ENV_TOKEN, token)
|
|
321
349
|
if not resolved_server:
|
|
@@ -323,6 +351,12 @@ def init(
|
|
|
323
351
|
if not resolved_token:
|
|
324
352
|
raise ConfigurationError("token is required")
|
|
325
353
|
|
|
354
|
+
# H-01: resolve the plaintext-transport opt-in. Env wins over the
|
|
355
|
+
# kwarg (same precedence as every other flag); default False.
|
|
356
|
+
resolved_allow_insecure = _env_bool(
|
|
357
|
+
_env.ENV_ALLOW_INSECURE_TRANSPORT, allow_insecure_transport
|
|
358
|
+
)
|
|
359
|
+
|
|
326
360
|
# KI20: normalize ``FLIGHTDECK_SERVER`` / ``server`` kwarg to
|
|
327
361
|
# always carry the ``/ingest`` suffix. The Claude Code plugin
|
|
328
362
|
# uses the same env var without the suffix (it appends
|
|
@@ -334,6 +368,17 @@ def init(
|
|
|
334
368
|
if "/ingest" not in resolved_server:
|
|
335
369
|
resolved_server = resolved_server.rstrip("/") + "/ingest"
|
|
336
370
|
|
|
371
|
+
# H-01: same silent-override guard for the control-plane API URL.
|
|
372
|
+
env_api_url = os.environ.get(_env.ENV_API_URL)
|
|
373
|
+
if env_api_url and api_url and env_api_url != api_url:
|
|
374
|
+
_log.warning(
|
|
375
|
+
"%s=%r overrides the api_url=%r passed to init(); using the "
|
|
376
|
+
"environment value. Unset %s if this was not intended.",
|
|
377
|
+
_env.ENV_API_URL,
|
|
378
|
+
env_api_url,
|
|
379
|
+
api_url,
|
|
380
|
+
_env.ENV_API_URL,
|
|
381
|
+
)
|
|
337
382
|
resolved_api_url = os.environ.get(_env.ENV_API_URL) or api_url
|
|
338
383
|
if not resolved_api_url:
|
|
339
384
|
resolved_api_url = resolved_server.rstrip("/").replace("/ingest", "/api")
|
|
@@ -419,6 +464,7 @@ def init(
|
|
|
419
464
|
"token": resolved_token,
|
|
420
465
|
"api_url": resolved_api_url,
|
|
421
466
|
"capture_prompts": capture,
|
|
467
|
+
"allow_insecure_transport": resolved_allow_insecure,
|
|
422
468
|
"unavailable_policy": os.environ.get(_env.ENV_UNAVAILABLE_POLICY, "continue"),
|
|
423
469
|
# Legacy wire-level ``flavor`` field stays populated for
|
|
424
470
|
# backward compat with every downstream surface that still
|
|
@@ -41,6 +41,9 @@ ENV_AGENT_FLAVOR_LEGACY = "AGENT_FLAVOR"
|
|
|
41
41
|
# ----------------------------------------------------------------
|
|
42
42
|
ENV_CAPTURE_PROMPTS = "FLIGHTDECK_CAPTURE_PROMPTS"
|
|
43
43
|
ENV_UNAVAILABLE_POLICY = "FLIGHTDECK_UNAVAILABLE_POLICY"
|
|
44
|
+
# H-01: opt-in to plaintext http:// egress for a non-local control
|
|
45
|
+
# plane. Bool-coerced; default False forces https:// for public hosts.
|
|
46
|
+
ENV_ALLOW_INSECURE_TRANSPORT = "FLIGHTDECK_ALLOW_INSECURE_TRANSPORT"
|
|
44
47
|
|
|
45
48
|
|
|
46
49
|
# Stable list of every FLIGHTDECK_* env var the sensor consults, in
|
|
@@ -56,4 +59,5 @@ ALL_ENV_VARS: tuple[str, ...] = (
|
|
|
56
59
|
ENV_HOSTNAME,
|
|
57
60
|
ENV_CAPTURE_PROMPTS,
|
|
58
61
|
ENV_UNAVAILABLE_POLICY,
|
|
62
|
+
ENV_ALLOW_INSECURE_TRANSPORT,
|
|
59
63
|
)
|
|
@@ -1273,6 +1273,11 @@ class Session:
|
|
|
1273
1273
|
# "what budget/MCP rules were in effect at session start"
|
|
1274
1274
|
# without joining time-windowed policy state.
|
|
1275
1275
|
payload["sensor_version"] = _sensor_version()
|
|
1276
|
+
# C-01: emit the session's capture posture authoritatively at
|
|
1277
|
+
# session_start so the server can persist whether this session
|
|
1278
|
+
# is capturing prompt content. Sourced directly from the
|
|
1279
|
+
# resolved config — the single source of truth for the flag.
|
|
1280
|
+
payload["capture_prompts"] = self.config.capture_prompts
|
|
1276
1281
|
iv = _collect_interceptor_versions()
|
|
1277
1282
|
if iv:
|
|
1278
1283
|
payload["interceptor_versions"] = iv
|
|
@@ -3,9 +3,87 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import enum
|
|
6
|
+
import ipaddress
|
|
6
7
|
import uuid
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
from typing import Any, Callable, Literal
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
# Schemes the sensor is willing to egress control-plane traffic over.
|
|
13
|
+
# Anything else (file://, ftp://, gopher://, a bare host with no
|
|
14
|
+
# scheme, ...) is rejected outright — the sensor only speaks HTTP(S)
|
|
15
|
+
# to the control plane, and an unexpected scheme is almost always a
|
|
16
|
+
# misconfiguration or an attempt to redirect egress somewhere it does
|
|
17
|
+
# not belong.
|
|
18
|
+
_ALLOWED_EGRESS_SCHEMES = frozenset({"https", "http"})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_local_host(host: str) -> bool:
|
|
22
|
+
"""Return True when *host* is a local/loopback/private target for
|
|
23
|
+
which plain ``http://`` is acceptable without an explicit opt-in.
|
|
24
|
+
|
|
25
|
+
Covers ``localhost`` (and ``*.localhost``), loopback IPs
|
|
26
|
+
(127.0.0.0/8, ::1), RFC1918 / ULA private ranges, and link-local
|
|
27
|
+
addresses. Everything else — public hostnames and public IPs —
|
|
28
|
+
is treated as remote and must use TLS unless the operator opts in.
|
|
29
|
+
"""
|
|
30
|
+
if not host:
|
|
31
|
+
return False
|
|
32
|
+
h = host.lower()
|
|
33
|
+
if h == "localhost" or h.endswith(".localhost"):
|
|
34
|
+
return True
|
|
35
|
+
try:
|
|
36
|
+
ip = ipaddress.ip_address(h)
|
|
37
|
+
except ValueError:
|
|
38
|
+
# Not an IP literal and not a localhost name → treat as a
|
|
39
|
+
# public host. DNS is deliberately NOT resolved here: the
|
|
40
|
+
# sensor must never make a blocking network call on the config
|
|
41
|
+
# path (Sensor Rule 27), and resolving would also open a
|
|
42
|
+
# rebinding hole. Operators pointing at a private hostname that
|
|
43
|
+
# only resolves to an RFC1918 address use the opt-in flag.
|
|
44
|
+
return False
|
|
45
|
+
return ip.is_loopback or ip.is_private or ip.is_link_local
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_egress_url(
|
|
49
|
+
url: str,
|
|
50
|
+
*,
|
|
51
|
+
field_name: str,
|
|
52
|
+
allow_insecure_transport: bool = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Validate a resolved control-plane egress URL (H-01).
|
|
55
|
+
|
|
56
|
+
Raises :class:`ValueError` when:
|
|
57
|
+
|
|
58
|
+
- the URL does not parse to a scheme in ``{"https", "http"}`` — this
|
|
59
|
+
rejects ``file://``, ``ftp://``, scheme-less strings, etc.; or
|
|
60
|
+
- the URL uses plain ``http://`` for a non-local host and the
|
|
61
|
+
operator has not opted into insecure transport
|
|
62
|
+
(``allow_insecure_transport`` / ``FLIGHTDECK_ALLOW_INSECURE_TRANSPORT``).
|
|
63
|
+
|
|
64
|
+
``field_name`` is the config field being checked (``"server"`` /
|
|
65
|
+
``"api_url"``) and is woven into the error message so a
|
|
66
|
+
misconfiguration is self-describing.
|
|
67
|
+
"""
|
|
68
|
+
parsed = urlparse(url)
|
|
69
|
+
scheme = parsed.scheme.lower()
|
|
70
|
+
if scheme not in _ALLOWED_EGRESS_SCHEMES:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"{field_name} must use an http:// or https:// URL; got "
|
|
73
|
+
f"scheme {scheme or '(none)'!r} in {url!r}. The sensor only "
|
|
74
|
+
"egresses control-plane traffic over HTTP(S)."
|
|
75
|
+
)
|
|
76
|
+
if scheme == "http" and not allow_insecure_transport:
|
|
77
|
+
host = parsed.hostname or ""
|
|
78
|
+
if not _is_local_host(host):
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"{field_name} uses insecure http:// for non-local host "
|
|
81
|
+
f"{host or '(none)'!r} ({url!r}). Use https://, or opt in "
|
|
82
|
+
"to plaintext transport by setting allow_insecure_transport"
|
|
83
|
+
"=True (env FLIGHTDECK_ALLOW_INSECURE_TRANSPORT=true). "
|
|
84
|
+
"Plaintext control-plane traffic to a remote host exposes "
|
|
85
|
+
"the access token and event payloads on the wire."
|
|
86
|
+
)
|
|
9
87
|
|
|
10
88
|
|
|
11
89
|
class SessionState(enum.Enum):
|
|
@@ -215,6 +293,13 @@ class SensorConfig:
|
|
|
215
293
|
client_type: str = "flightdeck_sensor"
|
|
216
294
|
api_url: str = ""
|
|
217
295
|
capture_prompts: bool = False
|
|
296
|
+
# H-01: opt-in to plaintext http:// egress for a non-local control
|
|
297
|
+
# plane. Default False forces https:// for any public host; local /
|
|
298
|
+
# loopback / private targets are always allowed over http regardless.
|
|
299
|
+
# Env-readable via FLIGHTDECK_ALLOW_INSECURE_TRANSPORT (resolved in
|
|
300
|
+
# init()). Never flip this on for a real deployment — it exposes the
|
|
301
|
+
# access token and event payloads on the wire.
|
|
302
|
+
allow_insecure_transport: bool = False
|
|
218
303
|
unavailable_policy: str = "continue"
|
|
219
304
|
agent_flavor: str = "unknown"
|
|
220
305
|
agent_type: str = "production"
|
|
@@ -233,6 +318,19 @@ class SensorConfig:
|
|
|
233
318
|
def __post_init__(self) -> None:
|
|
234
319
|
if not self.api_url:
|
|
235
320
|
self.api_url = self.server
|
|
321
|
+
# H-01: validate the resolved control-plane egress URLs. Both
|
|
322
|
+
# ``server`` (ingestion) and ``api_url`` (control-plane API) are
|
|
323
|
+
# checked so every egress target is covered by the single helper.
|
|
324
|
+
validate_egress_url(
|
|
325
|
+
self.server,
|
|
326
|
+
field_name="server",
|
|
327
|
+
allow_insecure_transport=self.allow_insecure_transport,
|
|
328
|
+
)
|
|
329
|
+
validate_egress_url(
|
|
330
|
+
self.api_url,
|
|
331
|
+
field_name="api_url",
|
|
332
|
+
allow_insecure_transport=self.allow_insecure_transport,
|
|
333
|
+
)
|
|
236
334
|
|
|
237
335
|
|
|
238
336
|
# Default directive grace period (ms): the window an agent gets to wind down
|
|
@@ -4,10 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flightdeck-sensor"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "In-process agent observability sensor for Flightdeck"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Omri Arnon", email = "omri.arnon@gmail.com" },
|
|
13
|
+
]
|
|
11
14
|
requires-python = ">=3.10,<3.14"
|
|
12
15
|
dependencies = [
|
|
13
16
|
"pydantic>=2.0",
|
|
@@ -24,6 +27,9 @@ classifiers = [
|
|
|
24
27
|
"Typing :: Typed",
|
|
25
28
|
]
|
|
26
29
|
|
|
30
|
+
[project.urls]
|
|
31
|
+
Repository = "https://github.com/flightdeckhq/flightdeck"
|
|
32
|
+
|
|
27
33
|
[project.optional-dependencies]
|
|
28
34
|
anthropic = ["anthropic>=0.20"]
|
|
29
35
|
openai = ["openai>=1.0", "tiktoken>=0.5"]
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""H-01 — control-plane egress URL validation.
|
|
2
|
+
|
|
3
|
+
The sensor rejects non-HTTP(S) schemes outright and refuses plaintext
|
|
4
|
+
``http://`` to a non-local control plane unless the operator explicitly
|
|
5
|
+
opts in (``allow_insecure_transport`` / ``FLIGHTDECK_ALLOW_INSECURE_TRANSPORT``).
|
|
6
|
+
Local / loopback / private hosts are always allowed over http. When an
|
|
7
|
+
env var overrides an explicitly-passed ``server`` / ``api_url``, init()
|
|
8
|
+
warns naming both values (silent override is the vuln).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from contextlib import contextmanager
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
import flightdeck_sensor
|
|
20
|
+
from flightdeck_sensor.core.types import SensorConfig, validate_egress_url
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def _clean_env() -> Iterator[None]:
|
|
28
|
+
saved = {k: v for k, v in os.environ.items() if k.startswith(("FLIGHTDECK_", "AGENT_"))}
|
|
29
|
+
for k in list(saved):
|
|
30
|
+
os.environ.pop(k, None)
|
|
31
|
+
try:
|
|
32
|
+
yield
|
|
33
|
+
finally:
|
|
34
|
+
for k in [k for k in os.environ if k.startswith(("FLIGHTDECK_", "AGENT_"))]:
|
|
35
|
+
os.environ.pop(k, None)
|
|
36
|
+
os.environ.update(saved)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@contextmanager
|
|
40
|
+
def _no_session() -> Iterator[None]:
|
|
41
|
+
flightdeck_sensor._session = None
|
|
42
|
+
flightdeck_sensor._client = None
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
if flightdeck_sensor._session is not None:
|
|
47
|
+
try:
|
|
48
|
+
flightdeck_sensor.teardown()
|
|
49
|
+
except Exception:
|
|
50
|
+
flightdeck_sensor._session = None
|
|
51
|
+
flightdeck_sensor._client = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Helper-level (validate_egress_url) tests
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
"url",
|
|
61
|
+
[
|
|
62
|
+
"file:///etc/passwd",
|
|
63
|
+
"ftp://example.com/x",
|
|
64
|
+
"gopher://example.com",
|
|
65
|
+
"ws://example.com/ingest",
|
|
66
|
+
"example.com/ingest", # scheme-less
|
|
67
|
+
"", # empty
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
def test_rejects_non_http_schemes(url: str) -> None:
|
|
71
|
+
with pytest.raises(ValueError, match="http:// or https://"):
|
|
72
|
+
validate_egress_url(url, field_name="server")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize(
|
|
76
|
+
"url",
|
|
77
|
+
[
|
|
78
|
+
"http://localhost:9999/ingest",
|
|
79
|
+
"http://localhost/ingest",
|
|
80
|
+
"http://sub.localhost/ingest",
|
|
81
|
+
"http://127.0.0.1:4000/ingest",
|
|
82
|
+
"http://127.5.5.5/ingest",
|
|
83
|
+
"http://10.0.0.4/ingest",
|
|
84
|
+
"http://192.168.1.10:4000/ingest",
|
|
85
|
+
"http://172.16.0.1/ingest",
|
|
86
|
+
"http://[::1]/ingest",
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
def test_allows_http_for_local_and_private_hosts(url: str) -> None:
|
|
90
|
+
# Must not raise.
|
|
91
|
+
validate_egress_url(url, field_name="server")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.parametrize(
|
|
95
|
+
"url",
|
|
96
|
+
[
|
|
97
|
+
"http://stack.internal/ingest",
|
|
98
|
+
"http://example.com/ingest",
|
|
99
|
+
"http://8.8.8.8/ingest",
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_rejects_http_for_non_local_host_without_optin(url: str) -> None:
|
|
103
|
+
with pytest.raises(ValueError, match="insecure http"):
|
|
104
|
+
validate_egress_url(url, field_name="server")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.mark.parametrize(
|
|
108
|
+
"url",
|
|
109
|
+
[
|
|
110
|
+
"http://stack.internal/ingest",
|
|
111
|
+
"http://example.com/ingest",
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
def test_allows_http_non_local_with_optin(url: str) -> None:
|
|
115
|
+
validate_egress_url(url, field_name="server", allow_insecure_transport=True)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize(
|
|
119
|
+
"url",
|
|
120
|
+
[
|
|
121
|
+
"https://stack.internal/ingest",
|
|
122
|
+
"https://example.com/ingest",
|
|
123
|
+
],
|
|
124
|
+
)
|
|
125
|
+
def test_allows_https_for_any_host(url: str) -> None:
|
|
126
|
+
validate_egress_url(url, field_name="server")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
# SensorConfig-level tests
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_config_rejects_non_local_http_by_default() -> None:
|
|
135
|
+
with pytest.raises(ValueError):
|
|
136
|
+
SensorConfig(server="http://stack.internal/ingest", token="tok")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_config_allows_non_local_http_with_optin() -> None:
|
|
140
|
+
cfg = SensorConfig(
|
|
141
|
+
server="http://stack.internal/ingest",
|
|
142
|
+
token="tok",
|
|
143
|
+
allow_insecure_transport=True,
|
|
144
|
+
)
|
|
145
|
+
assert cfg.server == "http://stack.internal/ingest"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_config_validates_api_url_too() -> None:
|
|
149
|
+
# server is https (fine) but api_url is a bad scheme → still rejected.
|
|
150
|
+
with pytest.raises(ValueError, match="api_url"):
|
|
151
|
+
SensorConfig(
|
|
152
|
+
server="https://stack.internal/ingest",
|
|
153
|
+
token="tok",
|
|
154
|
+
api_url="ftp://stack.internal/api",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_config_defaults_secure_transport_off() -> None:
|
|
159
|
+
cfg = SensorConfig(server="https://x.example/ingest", token="tok")
|
|
160
|
+
assert cfg.allow_insecure_transport is False
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
# init()-level tests
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_init_rejects_non_local_http() -> None:
|
|
169
|
+
with _clean_env(), _no_session(), pytest.raises(ValueError):
|
|
170
|
+
flightdeck_sensor.init(
|
|
171
|
+
server="http://stack.internal/ingest",
|
|
172
|
+
token="tok",
|
|
173
|
+
quiet=True,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_init_env_flag_enables_insecure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
178
|
+
with _clean_env(), _no_session():
|
|
179
|
+
monkeypatch.setattr(
|
|
180
|
+
"flightdeck_sensor.core.session.Session.start",
|
|
181
|
+
lambda self: None,
|
|
182
|
+
)
|
|
183
|
+
os.environ["FLIGHTDECK_ALLOW_INSECURE_TRANSPORT"] = "true"
|
|
184
|
+
flightdeck_sensor.init(
|
|
185
|
+
server="http://stack.internal/ingest",
|
|
186
|
+
token="tok",
|
|
187
|
+
quiet=True,
|
|
188
|
+
)
|
|
189
|
+
assert flightdeck_sensor._session is not None
|
|
190
|
+
assert flightdeck_sensor._session.config.allow_insecure_transport is True
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_init_warns_on_env_server_override(
|
|
194
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
195
|
+
caplog: pytest.LogCaptureFixture,
|
|
196
|
+
) -> None:
|
|
197
|
+
with _clean_env(), _no_session():
|
|
198
|
+
monkeypatch.setattr(
|
|
199
|
+
"flightdeck_sensor.core.session.Session.start",
|
|
200
|
+
lambda self: None,
|
|
201
|
+
)
|
|
202
|
+
os.environ["FLIGHTDECK_SERVER"] = "http://localhost:5000/ingest"
|
|
203
|
+
with caplog.at_level("WARNING"):
|
|
204
|
+
flightdeck_sensor.init(
|
|
205
|
+
server="http://localhost:9999/ingest",
|
|
206
|
+
token="tok",
|
|
207
|
+
quiet=True,
|
|
208
|
+
)
|
|
209
|
+
# Both the env value and the passed value are named in the warning.
|
|
210
|
+
joined = "\n".join(r.getMessage() for r in caplog.records)
|
|
211
|
+
assert "FLIGHTDECK_SERVER" in joined
|
|
212
|
+
assert "localhost:5000" in joined
|
|
213
|
+
assert "localhost:9999" in joined
|
|
214
|
+
# Env precedence still holds.
|
|
215
|
+
assert flightdeck_sensor._session is not None
|
|
216
|
+
assert flightdeck_sensor._session.config.server == "http://localhost:5000/ingest"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_init_no_warn_when_env_matches_or_absent(
|
|
220
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
221
|
+
caplog: pytest.LogCaptureFixture,
|
|
222
|
+
) -> None:
|
|
223
|
+
with _clean_env(), _no_session():
|
|
224
|
+
monkeypatch.setattr(
|
|
225
|
+
"flightdeck_sensor.core.session.Session.start",
|
|
226
|
+
lambda self: None,
|
|
227
|
+
)
|
|
228
|
+
with caplog.at_level("WARNING"):
|
|
229
|
+
flightdeck_sensor.init(
|
|
230
|
+
server="http://localhost:9999/ingest",
|
|
231
|
+
token="tok",
|
|
232
|
+
quiet=True,
|
|
233
|
+
)
|
|
234
|
+
joined = "\n".join(r.getMessage() for r in caplog.records)
|
|
235
|
+
assert "overrides the server" not in joined
|
|
@@ -420,7 +420,12 @@ def test_init_appends_ingest_suffix_when_missing(
|
|
|
420
420
|
monkeypatch.delenv("FLIGHTDECK_SERVER", raising=False)
|
|
421
421
|
monkeypatch.delenv("FLIGHTDECK_TOKEN", raising=False)
|
|
422
422
|
flightdeck_sensor.teardown()
|
|
423
|
-
flightdeck_sensor.init(
|
|
423
|
+
flightdeck_sensor.init(
|
|
424
|
+
server="http://stack.internal",
|
|
425
|
+
token="tok",
|
|
426
|
+
quiet=True,
|
|
427
|
+
allow_insecure_transport=True,
|
|
428
|
+
)
|
|
424
429
|
try:
|
|
425
430
|
assert flightdeck_sensor._session is not None
|
|
426
431
|
assert flightdeck_sensor._session.config.server == "http://stack.internal/ingest"
|
|
@@ -445,6 +450,7 @@ def test_init_preserves_ingest_suffix_when_present(
|
|
|
445
450
|
server="http://stack.internal/ingest",
|
|
446
451
|
token="tok",
|
|
447
452
|
quiet=True,
|
|
453
|
+
allow_insecure_transport=True,
|
|
448
454
|
)
|
|
449
455
|
try:
|
|
450
456
|
assert flightdeck_sensor._session is not None
|
|
@@ -472,6 +478,7 @@ def test_init_preserves_ingest_with_trailing_slash(
|
|
|
472
478
|
server="http://stack.internal/ingest/",
|
|
473
479
|
token="tok",
|
|
474
480
|
quiet=True,
|
|
481
|
+
allow_insecure_transport=True,
|
|
475
482
|
)
|
|
476
483
|
try:
|
|
477
484
|
assert flightdeck_sensor._session is not None
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_session_lifecycle_enrichment.py
RENAMED
|
@@ -128,3 +128,51 @@ def test_other_event_types_skip_session_end_close_reason() -> None:
|
|
|
128
128
|
s = _make_session()
|
|
129
129
|
payload = s._build_payload(EventType.POST_CALL)
|
|
130
130
|
assert "close_reason" not in payload
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# C-01 — capture posture on session_start
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _make_session_with_capture(capture: bool) -> Session:
|
|
139
|
+
config = SensorConfig(
|
|
140
|
+
server="http://localhost/ingest",
|
|
141
|
+
token="tok_dev",
|
|
142
|
+
agent_id=str(uuid.uuid4()),
|
|
143
|
+
agent_name="test-agent",
|
|
144
|
+
user_name="test",
|
|
145
|
+
hostname="test-host",
|
|
146
|
+
client_type="flightdeck_sensor",
|
|
147
|
+
api_url="http://localhost/api",
|
|
148
|
+
agent_flavor="e2e-test",
|
|
149
|
+
agent_type="coding",
|
|
150
|
+
session_id=str(uuid.uuid4()),
|
|
151
|
+
capture_prompts=capture,
|
|
152
|
+
quiet=True,
|
|
153
|
+
)
|
|
154
|
+
s = Session(config, client=MagicMock())
|
|
155
|
+
s.event_queue = MagicMock()
|
|
156
|
+
return s
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_session_start_carries_capture_prompts_true() -> None:
|
|
160
|
+
s = _make_session_with_capture(True)
|
|
161
|
+
payload = s._build_payload(EventType.SESSION_START)
|
|
162
|
+
assert payload["capture_prompts"] is True
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_session_start_carries_capture_prompts_false() -> None:
|
|
166
|
+
s = _make_session_with_capture(False)
|
|
167
|
+
payload = s._build_payload(EventType.SESSION_START)
|
|
168
|
+
# Present and correct — the field must ship even when off so the
|
|
169
|
+
# server records the posture authoritatively.
|
|
170
|
+
assert payload["capture_prompts"] is False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_session_start_capture_prompts_default_false() -> None:
|
|
174
|
+
"""The default _make_session helper (no capture kwarg) reflects the
|
|
175
|
+
sensor's False default posture on session_start."""
|
|
176
|
+
s = _make_session()
|
|
177
|
+
payload = s._build_payload(EventType.SESSION_START)
|
|
178
|
+
assert payload["capture_prompts"] is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/__init__.py
RENAMED
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/langgraph.py
RENAMED
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/litellm.py
RENAMED
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/interceptor/mcp_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/flightdeck_sensor/providers/anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_agent_type_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_d150_tool_capture_destination.py
RENAMED
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_framework_attribution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_interceptor_langgraph.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_policy_emission_enriched.py
RENAMED
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_mcp_policy_enforcement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy_decision_summary.py
RENAMED
|
File without changes
|
{flightdeck_sensor-0.5.2 → flightdeck_sensor-0.5.3}/tests/unit/test_policy_emission_enriched.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|