oc_connect
Loading...
Searching...
No Matches
oc_connect.session_alerts Namespace Reference

Classes

class  SessionAlertError
class  SessionAlertRelayError
class  SessionAlertEvent
class  SessionAlertStore
class  SessionAlertOutcome
class  SessionAlertRelay

Functions

str render_event (SessionAlertEvent event)
None _atomic_write (Path path, str payload)

Variables

str SESSION_ALERT_LANE = "session-alerts"
str SESSION_ALERT_SOURCE = "product"
str SESSION_ALERT_SCHEMA_VERSION = "1.0"
 SessionAlertKind
str SESSION_ALERT_BEGIN = "<<<BEGIN SESSION ALERT {event_id}>>>"
str SESSION_ALERT_END = "<<<END SESSION ALERT {event_id}>>>"
str NEUTRALISED_MARKER = "<< <"

Detailed Description

The session-alert lane: the events worth waking an operator for, and their
one-line relay.

Plan 237 (CAPS-01), built to the shape plan 231 established for
``worker-reports``; plan 248 (CAPS-03) added the fourth kind. A running OC Chat
session that crosses a cost checkpoint, stops at the dollar hard cap, pauses at
the per-turn tool-call checkpoint, or halts a turn on a detected stall
writes ONE event into ``comms/lanes/session-alerts/`` and stops there: the
product holds no credential, knows nothing about connectors, and touches no
other lane. This module is the other half โ€” the orchestrator-side reader and
the grant-checked, egress-filtered relay that puts the alert on the operator's
phone.

**This module is the schema authority for the lane.** The writer lives in
``oc_chat/oc_chat/core/session_alerts.py`` and deliberately does NOT import
this package โ€” that is what keeps the product credential-free. The two sides
agree by written contract rather than by a shared import, and the contract is
:class:`SessionAlertEvent`::

    {
      "schema_version":   "1.0",
      "event_id":         "<26-character ULID, time-first so lexical == arrival>",
      "lane":             "session-alerts",
      "direction":        "inbound",
      "risk_class":       "low",
      "created_at_utc":   "2026-09-06T12:00:00Z",
      "session_id":       "<the chat session's stable uuid4>",
      "kind":             "cost_threshold" | "cost_hard_cap"
                          | "tool_call_checkpoint" | "stall_halt",
      "threshold_usd":    2.0,          // null for a tool-call checkpoint
                                        // and for a stall halt
      "total_usd":        2.14,
      "unpriced_samples": 3,
      "turn_tool_calls":  100,
      "summary":          "<the one line the operator reads, numbers only>",
      "relayed_at_utc":   null,
      "relay_chunk_count": null
    }

File naming follows the ยง3 bus convention, ``<utc-stamp>_product_<ulid>.json``.

Two differences from the worker-report lane are deliberate:

* **One line, never chunked.** A worker report carries the worker's whole
  closing summary and must be chunked to survive Telegram's 4096-character
  limit. An alert is a single generated sentence of numbers, so the relay sends
  exactly one message per event and records ``relay_chunk_count = 1``. If a
  summary ever grew past the transport limit the send would fail and the event
  would stay unrelayed โ€” a loud failure, not a truncation.
* **No content digest.** A worker report's ``summary_sha256`` exists because the
  text is a human's words and a post-hoc edit would put words in their mouth.
  An alert's summary is derived from the numeric fields beside it, so the
  relay re-derives nothing and asserts nothing about authorship; what it
  forwards is the stored line, fenced, exactly as it forwards a worker's.

Dependencies
------------
- oc_connect.audit (AuditStore, content_hash, timestamp, utc_now - audits relay outcomes and stamps relayed_at_utc)
- oc_connect.delivery (OutboundSender - the grant-checked send path every relayed alert crosses)
- oc_connect.egress (require_safe, EgressBlockedError - scans the alert summary before it is queued)
- oc_diagnostics (_dbg structured logger)
- option_c_connector_contracts (lane - the session-alerts lane's declared direction and risk ceiling)
- pydantic (BaseModel, ConfigDict, field_validator - the SessionAlertEvent schema)

Function Documentation

◆ _atomic_write()

None oc_connect.session_alerts._atomic_write ( Path path,
str payload )
protected

Definition at line 179 of file session_alerts.py.

Here is the caller graph for this function:

◆ render_event()

str oc_connect.session_alerts.render_event ( SessionAlertEvent event)
Return the one line an operator reads for this alert, fenced.

Definition at line 164 of file session_alerts.py.

Variable Documentation

◆ NEUTRALISED_MARKER

str oc_connect.session_alerts.NEUTRALISED_MARKER = "<< <"

Definition at line 103 of file session_alerts.py.

◆ SESSION_ALERT_BEGIN

str oc_connect.session_alerts.SESSION_ALERT_BEGIN = "<<<BEGIN SESSION ALERT {event_id}>>>"

Definition at line 99 of file session_alerts.py.

◆ SESSION_ALERT_END

str oc_connect.session_alerts.SESSION_ALERT_END = "<<<END SESSION ALERT {event_id}>>>"

Definition at line 100 of file session_alerts.py.

◆ SESSION_ALERT_LANE

str oc_connect.session_alerts.SESSION_ALERT_LANE = "session-alerts"

Definition at line 85 of file session_alerts.py.

◆ SESSION_ALERT_SCHEMA_VERSION

str oc_connect.session_alerts.SESSION_ALERT_SCHEMA_VERSION = "1.0"

Definition at line 90 of file session_alerts.py.

◆ SESSION_ALERT_SOURCE

str oc_connect.session_alerts.SESSION_ALERT_SOURCE = "product"

Definition at line 88 of file session_alerts.py.

◆ SessionAlertKind

oc_connect.session_alerts.SessionAlertKind
Initial value:
1= Literal[
2 "cost_threshold", "cost_hard_cap", "tool_call_checkpoint", "stall_halt",
3]

Definition at line 92 of file session_alerts.py.