#!/usr/bin/env python3
"""
Can an agent's tool call be filed as a trading order record?

RTS 24 (Commission Delegated Regulation (EU) 2017/580) Annex, Table 2 lists the
51 fields an EU trading venue must keep for every order, per MiFIR Art 25(2).
This script walks all 51 and asks, for one agent tool call instrumented with
everything the agent stack currently standardises (an OpenTelemetry GenAI
execute_tool span, the MCP request, an AuthZEN/COAZ-MCP evaluation and the
OAuth token claims), whether a source for the field exists.

The mapping is a judgment, declared in full below so you can disagree with any
line. Three answers per field:

  N/A     the field is about prices, venues or order books and has no analogue
          for a tool call
  SOURCE  the agent stack carries a standard field that fills it
  WEAK    a field exists but is optional, unversioned or unsigned
  NONE    the field applies to an agent action and nothing standard fills it

Read-only, stdlib only. Usage: python3 part3-order-record-scorecard.py

Frame and exclusions, so the numbers can be argued with:

  Fields      Commission Delegated Regulation (EU) 2017/580 (RTS 24), Annex,
              Table 2, read from EUR-Lex CELEX 32017R0580 on 2026-09-08.
              Numbers 1 to 51 and the field names follow that table. Names for
              fields 4, 5, 16 and 21 are shortened to fit a terminal; the
              regulation's wording governs. RTS 24 Article 1(1) requires these
              details "insofar as they pertain to the order concerned", so a
              real venue would leave some blank for some orders too.
  Agent stack OpenTelemetry GenAI semantic conventions read at main in
              open-telemetry/semantic-conventions-genai on 2026-09-08 (the
              repo has no tagged release); MCP revision 2026-07-28; OpenID
              AuthZEN 1.0 Final with the COAZ-MCP binding, Draft 1 of
              2026-02-13; RFC 9068 and RFC 7519 token claims.
  Span        The subject is the execute_tool span. Attributes that appear
              only on create_agent or invoke_agent, such as gen_ai.agent.id
              and gen_ai.agent.version, are not counted as sources here, and
              the reason strings say so where it matters.
  Opt-in      Opt-In attributes are not counted as sources. Anything the
              conventions mark Recommended or Conditionally Required counts
              as WEAK, not SOURCE, because a conforming emitter may omit it.
  Not covered vendor-specific attributes, private extensions, anything a team
              could add locally, and any framework that is not a published
              specification. A stack that adds its own field can fill several
              of these; the question here is what a standard supplies.
"""

# (number, RTS 24 field name, verdict, agent-stack source or reason)
FIELDS = [
 (1,  "Identification of the entity which submitted the order", "WEAK",
      "COAZ context.agent = $token.?client_id, optional inside an optional object"),
 (2,  "Direct Electronic Access (DEA)", "NONE",
      "no standard flag for 'direct vs via gateway' on an agent call"),
 (3,  "Client identification code", "SOURCE",
      "the human principal: COAZ subject.id = $token.sub, required"),
 (4,  "Investment decision within firm (person or algorithm)", "NONE",
      "no unique-over-time identifier for the deciding agent on this span; gen_ai.agent.name\n       is a human-readable label the application supplies"),
 (5,  "Execution within firm (person or algorithm)", "WEAK",
      "gen_ai.agent.name, Conditionally Required, human-readable, not required to be unique"),
 (6,  "Non-executing broker", "N/A", "market structure"),
 (7,  "Trading capacity", "N/A", "market structure"),
 (8,  "Liquidity provision activity", "N/A", "market structure"),
 (9,  "Date and Time", "WEAK",
      "span start_time exists; nothing in the agent stack meets the RTS 25 clock rules"),
 (10, "Validity period", "SOURCE", "token exp is a validity period"),
 (11, "Order restriction", "N/A", "order book semantics"),
 (12, "Validity period and time", "SOURCE", "token exp timestamp"),
 (13, "Priority time stamp", "N/A", "matching engine"),
 (14, "Priority size", "N/A", "matching engine"),
 (15, "Sequence number", "NONE",
      "no per-server monotonic sequence for agent calls; spans order by unsynchronised timestamps"),
 (16, "Segment MIC code (venue)", "SOURCE", "the resource server: token aud / RFC 8707 resource"),
 (17, "Order book code", "N/A", "order book"),
 (18, "Financial instrument identification code", "SOURCE",
      "the thing acted on: gen_ai.tool.name, Required"),
 (19, "Date of receipt", "WEAK", "server-side span timestamp, if the server is instrumented"),
 (20, "Order identification code", "WEAK",
      "gen_ai.tool.call.id, Recommended If available, so a conforming span may omit it"),
 (21, "Event: new / modified / cancelled / rejected / executed", "WEAK",
      "span status and error.type; no lifecycle enumeration, and over Streamable HTTP a\n       cancellation is a closed stream with no message"),
 (22, "Order type", "N/A", "order semantics"),
 (23, "Order type classification", "N/A", "order semantics"),
 (24, "Limit price", "N/A", "price"),
 (25, "Additional limit price", "N/A", "price"),
 (26, "Stop price", "N/A", "price"),
 (27, "Pegged limit price", "N/A", "price"),
 (28, "Transaction price", "N/A", "price"),
 (29, "Price currency", "N/A", "price"),
 (30, "Currency of leg 2", "N/A", "price"),
 (31, "Price notation", "N/A", "price"),
 (32, "Buy-sell indicator", "N/A", "direction of trade"),
 (33, "Order status", "SOURCE", "span status, error.type"),
 (34, "Quantity notation", "N/A", "quantity"),
 (35, "Quantity currency", "N/A", "quantity"),
 (36, "Initial quantity", "N/A", "quantity"),
 (37, "Remaining quantity including hidden", "N/A", "quantity"),
 (38, "Displayed quantity", "N/A", "quantity"),
 (39, "Traded quantity", "N/A", "quantity"),
 (40, "Minimum Acceptable Quantity", "N/A", "quantity"),
 (41, "Minimum executable size", "N/A", "quantity"),
 (42, "MES first execution only", "N/A", "quantity"),
 (43, "Passive only indicator", "N/A", "order book"),
 (44, "Passive or aggressive indicator", "N/A", "order book"),
 (45, "Self-Execution Prevention", "N/A", "order book"),
 (46, "Strategy Linked Order identification", "WEAK",
      "parent span / trace id links the call to its task, and W3C traceparent is unsigned"),
 (47, "Routing strategy", "NONE",
      "which gateway or sub-agent path the call took: not recorded anywhere standard"),
 (48, "Trading venue transaction identification code", "WEAK",
      "a server-side call id only if the resource server emits one"),
 (49, "Trading phases", "N/A", "venue state"),
 (50, "Indicative auction price", "N/A", "auction"),
 (51, "Indicative auction volume", "N/A", "auction"),
]

def main():
    assert len(FIELDS) == 51 and [f[0] for f in FIELDS] == list(range(1, 52))
    counts = {k: 0 for k in ("N/A", "SOURCE", "WEAK", "NONE")}
    for _, _, v, _ in FIELDS:
        counts[v] += 1
    applicable = 51 - counts["N/A"]
    print("RTS 24 Annex Table 2: 51 fields per order. Filed against one agent tool call.\n")
    print(f"{'#':>2}  {'field':<58} {'verdict':<7} source / reason")
    print("-" * 110)
    for n, name, v, why in FIELDS:
        if v != "N/A":
            print(f"{n:>2}  {name[:58]:<58} {v:<7} {why}")
    print("-" * 110)
    print(f"not applicable to a tool call   {counts['N/A']:>2} / 51   (prices, quantities, order books, venue state)")
    print(f"applicable                      {applicable:>2} / 51")
    print(f"  filled by a standard field     {counts['SOURCE']:>2}")
    print(f"  weak: optional / unversioned / unsigned  {counts['WEAK']:>2}")
    print(f"  nothing standard fills it      {counts['NONE']:>2}")
    print("\nThe two identity fields the regulation cares about most, 4 and 5, are NONE and WEAK.")
    print("Field 4 wants a person or an algorithm with an identifier that is unique over time")
    print("(RTS 22 Art 8(3)). The execute_tool span carries no agent identifier at all,")
    print("and gen_ai.agent.id, which appears on create_agent and invoke_agent, is not")
    print("required to be stable across versions.")

if __name__ == "__main__":
    main()
