Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## Table of Contents

- [What's new (2026-06-21) — W3C Trace Context Propagation](#whats-new-2026-06-21--w3c-trace-context-propagation)
- [What's new (2026-06-21) — HTTP Record & Replay Cassette](#whats-new-2026-06-21--http-record--replay-cassette)
- [What's new (2026-06-21) — Bulkhead & Rate-Limit Headers](#whats-new-2026-06-21--bulkhead--rate-limit-headers)
- [What's new (2026-06-21) — Streaming Latency Percentiles](#whats-new-2026-06-21--streaming-latency-percentiles)
Expand Down Expand Up @@ -128,6 +129,12 @@

---

## What's new (2026-06-21) — W3C Trace Context Propagation

Correlate spans and logs across HTTP boundaries. Full reference: [`docs/source/Eng/doc/new_features/v76_features_doc.rst`](docs/source/Eng/doc/new_features/v76_features_doc.rst).

- **`SpanContext` / `new_root_context` / `child_context` / `inject_context` / `extract_context`** (`AC_trace_inject`, `AC_trace_extract`): the existing tracer and `agent_trace` spans carried no IDs, so a span on one side of an HTTP call couldn't be correlated with the work it triggered on the other. This implements the W3C Trace Context standard — generate/parse/propagate `traceparent` + `tracestate` headers (version-`00`, rejects malformed/all-zero IDs), with an injectable RNG for deterministic IDs in tests. Pure-stdlib.

## What's new (2026-06-21) — HTTP Record & Replay Cassette

Re-run API flows in CI with no live server. Full reference: [`docs/source/Eng/doc/new_features/v75_features_doc.rst`](docs/source/Eng/doc/new_features/v75_features_doc.rst).
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目录

- [本次更新 (2026-06-21) — W3C Trace Context 传播](#本次更新-2026-06-21--w3c-trace-context-传播)
- [本次更新 (2026-06-21) — HTTP 录制与重播卡带](#本次更新-2026-06-21--http-录制与重播卡带)
- [本次更新 (2026-06-21) — 隔舱与速率限制标头](#本次更新-2026-06-21--隔舱与速率限制标头)
- [本次更新 (2026-06-21) — 流式延迟百分位](#本次更新-2026-06-21--流式延迟百分位)
Expand Down Expand Up @@ -127,6 +128,12 @@

---

## 本次更新 (2026-06-21) — W3C Trace Context 传播

跨 HTTP 边界关联 span 与日志。完整参考:[`docs/source/Zh/doc/new_features/v76_features_doc.rst`](../docs/source/Zh/doc/new_features/v76_features_doc.rst)。

- **`SpanContext` / `new_root_context` / `child_context` / `inject_context` / `extract_context`**(`AC_trace_inject`、`AC_trace_extract`):既有追踪器与 `agent_trace` 的 span 不带 ID,因此一次 HTTP 调用一端的 span 无法与它在另一端触发的工作关联。本功能实现 W3C Trace Context 标准 —— 生成/解析/传播 `traceparent` + `tracestate` 标头(version-`00`,拒绝格式不符/全零 ID),并以可注入 RNG 让测试中的 ID 确定。纯标准库。

## 本次更新 (2026-06-21) — HTTP 录制与重播卡带

在 CI 中重跑 API 流程,无需在线服务器。完整参考:[`docs/source/Zh/doc/new_features/v75_features_doc.rst`](../docs/source/Zh/doc/new_features/v75_features_doc.rst)。
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目錄

- [本次更新 (2026-06-21) — W3C Trace Context 傳播](#本次更新-2026-06-21--w3c-trace-context-傳播)
- [本次更新 (2026-06-21) — HTTP 錄製與重播卡帶](#本次更新-2026-06-21--http-錄製與重播卡帶)
- [本次更新 (2026-06-21) — 隔艙與速率限制標頭](#本次更新-2026-06-21--隔艙與速率限制標頭)
- [本次更新 (2026-06-21) — 串流延遲百分位](#本次更新-2026-06-21--串流延遲百分位)
Expand Down Expand Up @@ -127,6 +128,12 @@

---

## 本次更新 (2026-06-21) — W3C Trace Context 傳播

跨 HTTP 邊界關聯 span 與日誌。完整參考:[`docs/source/Zh/doc/new_features/v76_features_doc.rst`](../docs/source/Zh/doc/new_features/v76_features_doc.rst)。

- **`SpanContext` / `new_root_context` / `child_context` / `inject_context` / `extract_context`**(`AC_trace_inject`、`AC_trace_extract`):既有追蹤器與 `agent_trace` 的 span 不帶 ID,因此一次 HTTP 呼叫一端的 span 無法與它在另一端觸發的工作關聯。本功能實作 W3C Trace Context 標準 —— 產生/解析/傳播 `traceparent` + `tracestate` 標頭(version-`00`,拒絕格式不符/全零 ID),並以可注入 RNG 讓測試中的 ID 具決定性。純標準函式庫。

## 本次更新 (2026-06-21) — HTTP 錄製與重播卡帶

在 CI 中重跑 API 流程,無需線上伺服器。完整參考:[`docs/source/Zh/doc/new_features/v75_features_doc.rst`](../docs/source/Zh/doc/new_features/v75_features_doc.rst)。
Expand Down
51 changes: 51 additions & 0 deletions docs/source/Eng/doc/new_features/v76_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
W3C Trace Context Propagation
=============================

The ``observability`` tracer and ``agent_trace`` spans carry no IDs, so a span
on one side of an HTTP call could not be correlated with the work it triggered
on the other. This adds the W3C Trace Context standard — generate, parse, and
propagate ``traceparent`` / ``tracestate`` headers so spans, logs, and
downstream services all share one trace and span lineage.

Pure standard library (``os`` / ``re``); imports no ``PySide6``. ID generation
takes an injectable RNG, so trace and span IDs are deterministic under test.

Headless API
------------

.. code-block:: python

from je_auto_control import (
new_root_context, child_context, inject_context, extract_context,
parse_traceparent, format_traceparent,
)

# Start a trace and propagate it onto an outgoing request:
ctx = new_root_context()
headers = inject_context({"accept": "application/json"}, ctx)
# headers["traceparent"] == "00-<32 hex>-<16 hex>-01"

# On the receiving side, continue the same trace:
parent = extract_context(request_headers)
if parent is not None:
span = child_context(parent) # same trace_id, new span_id

``SpanContext`` is the immutable (``trace_id``, ``span_id``, ``trace_flags``,
``tracestate``) tuple. ``new_root_context`` mints a fresh trace; ``child_context``
keeps the trace id and inherited state but allocates a new span id.
``parse_traceparent`` / ``format_traceparent`` round-trip the version-``00``
header (rejecting bad versions, malformed or all-zero IDs with
``TraceContextError``); ``parse_tracestate`` / ``format_tracestate`` handle the
vendor list. ``inject_context`` writes the headers; ``extract_context`` reads
them back (case-insensitively).

Executor commands
-----------------

``AC_trace_inject`` propagates a context onto outgoing ``headers`` — with a
``traceparent`` it derives a child of that parent, otherwise it starts a fresh
root — and returns ``{headers, traceparent, trace_id, span_id}``.
``AC_trace_extract`` reads a context back out of request ``headers`` and returns
``{context}`` (or ``null`` when no ``traceparent`` is present). Both are exposed
as MCP tools (``ac_trace_inject`` / ``ac_trace_extract``) and as Script Builder
commands under **Data**.
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v73_features_doc
doc/new_features/v74_features_doc
doc/new_features/v75_features_doc
doc/new_features/v76_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
43 changes: 43 additions & 0 deletions docs/source/Zh/doc/new_features/v76_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
W3C Trace Context 傳播
=====================

``observability`` 追蹤器與 ``agent_trace`` 的 span 都不帶任何 ID,因此一次 HTTP 呼叫一端的 span
無法與它在另一端觸發的工作關聯起來。本功能加入 W3C Trace Context 標準 —— 產生、解析並傳播
``traceparent`` / ``tracestate`` 標頭,讓 span、日誌與下游服務共享同一條 trace 與 span 血緣。

純標準函式庫(``os`` / ``re``);不匯入 ``PySide6``。ID 產生可注入 RNG,因此 trace 與 span ID 在測試中
具決定性。

無頭 API
--------

.. code-block:: python

from je_auto_control import (
new_root_context, child_context, inject_context, extract_context,
parse_traceparent, format_traceparent,
)

# 開啟一條 trace 並傳播到外送請求:
ctx = new_root_context()
headers = inject_context({"accept": "application/json"}, ctx)
# headers["traceparent"] == "00-<32 hex>-<16 hex>-01"

# 接收端延續同一條 trace:
parent = extract_context(request_headers)
if parent is not None:
span = child_context(parent) # 相同 trace_id,新的 span_id

``SpanContext`` 是不可變的(``trace_id``、``span_id``、``trace_flags``、``tracestate``)組合。
``new_root_context`` 鑄造新 trace;``child_context`` 保留 trace id 與繼承狀態但配置新的 span id。
``parse_traceparent`` / ``format_traceparent`` 來回轉換 version-``00`` 標頭(對錯誤版本、格式不符或全零
ID 拋出 ``TraceContextError``);``parse_tracestate`` / ``format_tracestate`` 處理 vendor 清單。
``inject_context`` 寫入標頭;``extract_context`` 將其讀回(不分大小寫)。

執行器命令
----------

``AC_trace_inject`` 把 context 傳播到外送 ``headers`` —— 帶 ``traceparent`` 時衍生該父節點的子 span,
否則開啟新的 root —— 回傳 ``{headers, traceparent, trace_id, span_id}``。``AC_trace_extract`` 從請求
``headers`` 讀回 context,回傳 ``{context}``(無 ``traceparent`` 時為 ``null``)。兩者皆以 MCP 工具
(``ac_trace_inject`` / ``ac_trace_extract``)以及 Script Builder 中 **Data** 分類下的命令提供。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v73_features_doc
doc/new_features/v74_features_doc
doc/new_features/v75_features_doc
doc/new_features/v76_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@
)
# HTTP record/replay cassette (deterministic offline API tests)
from je_auto_control.utils.http_cassette import Cassette, CassetteMissError
# W3C Trace Context propagation (traceparent / tracestate)
from je_auto_control.utils.trace_context import (
SpanContext, TraceContextError, child_context, extract_context,
format_traceparent, format_tracestate, inject_context, new_root_context,
new_span_id, new_trace_id, parse_traceparent, parse_tracestate,
)
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
Expand Down Expand Up @@ -877,6 +883,10 @@ def start_autocontrol_gui(*args, **kwargs):
"Bulkhead", "BulkheadFullError", "next_delay", "parse_ratelimit",
"parse_retry_after",
"Cassette", "CassetteMissError",
"SpanContext", "TraceContextError", "child_context", "extract_context",
"format_traceparent", "format_tracestate", "inject_context",
"new_root_context", "new_span_id", "new_trace_id", "parse_traceparent",
"parse_tracestate",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
18 changes: 18 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,24 @@ def _add_resilience_specs(specs: List[CommandSpec]) -> None:
),
description="Replay a recorded HTTP response from a cassette (no network).",
))
specs.append(CommandSpec(
"AC_trace_inject", "Data", "Trace Context: Inject",
fields=(
FieldSpec("headers", FieldType.STRING, optional=True,
placeholder='{"accept": "application/json"}'),
FieldSpec("traceparent", FieldType.STRING, optional=True,
placeholder="00-<32 hex>-<16 hex>-01 (parent; omit for root)"),
),
description="Set a W3C traceparent on outgoing headers (root or child).",
))
specs.append(CommandSpec(
"AC_trace_extract", "Data", "Trace Context: Extract",
fields=(
FieldSpec("headers", FieldType.STRING,
placeholder='{"traceparent": "00-...-...-01"}'),
),
description="Extract the W3C trace context from request headers.",
))
specs.append(CommandSpec(
"AC_rate_limit", "Flow", "Rate Limit (Token Bucket)",
fields=(
Expand Down
31 changes: 31 additions & 0 deletions je_auto_control/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2971,6 +2971,35 @@ def _http_replay(cassette: Any, url: str,
return {"response": response}


def _trace_inject(headers: Any = None,
traceparent: Any = None) -> Dict[str, Any]:
"""Adapter: propagate a trace context into outgoing headers.

With ``traceparent`` set, derive a child span of that parent; otherwise
start a fresh root. Returns the updated ``headers`` plus the new ids.
"""
import json
from je_auto_control.utils.trace_context import (
child_context, inject_context, new_root_context, parse_traceparent)
if isinstance(headers, str):
headers = json.loads(headers)
ctx = (child_context(parse_traceparent(traceparent))
if traceparent else new_root_context())
return {"headers": inject_context(headers, ctx),
"traceparent": inject_context({}, ctx)["traceparent"],
"trace_id": ctx.trace_id, "span_id": ctx.span_id}


def _trace_extract(headers: Any) -> Dict[str, Any]:
"""Adapter: extract a trace context from request headers."""
import json
from je_auto_control.utils.trace_context import extract_context
if isinstance(headers, str):
headers = json.loads(headers)
ctx = extract_context(headers)
return {"context": ctx.to_dict() if ctx is not None else None}


def _percentiles(samples: Any, qs: Any = None) -> Dict[str, Any]:
"""Adapter: exact percentiles of a numeric sample list (or JSON string)."""
import json
Expand Down Expand Up @@ -4047,6 +4076,8 @@ def __init__(self):
"AC_bulkhead_run": _bulkhead_run,
"AC_retry_after": _retry_after,
"AC_http_replay": _http_replay,
"AC_trace_inject": _trace_inject,
"AC_trace_extract": _trace_extract,
"AC_unified_diff": _unified_diff,
"AC_apply_unified": _apply_unified,
"AC_three_way_merge": _three_way_merge,
Expand Down
27 changes: 27 additions & 0 deletions je_auto_control/utils/mcp_server/tools/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3343,6 +3343,32 @@ def rate_limit_tools() -> List[MCPTool]:
]


def trace_context_tools() -> List[MCPTool]:
return [
MCPTool(
name="ac_trace_inject",
description=("Propagate a W3C trace context into outgoing 'headers'. "
"With 'traceparent' set, derive a child span of that "
"parent; else start a fresh root. Returns updated "
"{headers, traceparent, trace_id, span_id}."),
input_schema=schema(
{"headers": {"type": "object"},
"traceparent": {"type": "string"}},
[]),
handler=h.trace_inject,
annotations=READ_ONLY,
),
MCPTool(
name="ac_trace_extract",
description=("Extract a W3C trace context from request 'headers'. "
"Returns {context} (or null when no traceparent)."),
input_schema=schema({"headers": {"type": "object"}}, ["headers"]),
handler=h.trace_extract,
annotations=READ_ONLY,
),
]


def http_cassette_tools() -> List[MCPTool]:
return [
MCPTool(
Expand Down Expand Up @@ -4878,6 +4904,7 @@ def media_assert_tools() -> List[MCPTool]:
search_index_tools, stats_tools, recurrence_tools, text_diff_tools,
feature_flag_tools, provenance_tools, json_contract_tools, chaos_tools,
slo_tools, percentiles_tools, bulkhead_tools, http_cassette_tools,
trace_context_tools,
saga_tools, decision_table_tools, locator_repair_tools,
pii_text_tools, sarif_tools,
screen_record_tools,
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/utils/mcp_server/tools/_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,16 @@ def http_replay(cassette, url, method="GET"):
return {"response": response}


def trace_inject(headers=None, traceparent=None):
from je_auto_control.utils.executor.action_executor import _trace_inject
return _trace_inject(headers, traceparent)


def trace_extract(headers):
from je_auto_control.utils.executor.action_executor import _trace_extract
return _trace_extract(headers)


def build_provenance(paths, builder_id="je_auto_control"):
from je_auto_control.utils.provenance import build_provenance, subject_for
subjects = [subject_for(path) for path in paths]
Expand Down
13 changes: 13 additions & 0 deletions je_auto_control/utils/trace_context/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""W3C Trace Context propagation for AutoControl runs."""
from je_auto_control.utils.trace_context.trace_context import (
SpanContext, TraceContextError, child_context, extract_context,
format_traceparent, format_tracestate, inject_context, new_root_context,
new_span_id, new_trace_id, parse_traceparent, parse_tracestate,
)

__all__ = [
"SpanContext", "TraceContextError", "child_context", "extract_context",
"format_traceparent", "format_tracestate", "inject_context",
"new_root_context", "new_span_id", "new_trace_id", "parse_traceparent",
"parse_tracestate",
]
Loading
Loading