> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/release-notes/2.0.md).

# 2.0

Release notes for Pharos Triton 2.0 — a breaking release with a static, builder-first API, distributed tracing with spans, and Apex performance marks.

> **Source commit:** [83ba761](https://github.com/Pharos-AI/triton/commit/83ba761ebdc38b948dffbaf37a26112ebb85076b)
>
> **Status:** Unreleased — 2.0 preview (targeting release)

{% hint style="danger" %}
**2.0 is a major release with breaking API changes.** Four changes require edits to existing code — three of them fail to compile:

* **The singleton entry point is gone.** `Triton.instance` has been replaced by a static, builder-first API; every `Triton.instance.*` call must be rewritten.
* **`TritonTypes.Category.Integration` is removed.** Callout and integration logging keeps the default `Apex` category — an integration log is identified by `.integrationPayload(request, response)` plus a call type such as `TritonTypes.Type.BackendCall`, not by a category of its own.
* **`TritonTypes.Category.Warning`, `.Event`, and `.Debug` are removed.** The enum is now exactly `Apex` · `Flow` · `LWC` · `Aura`. Category answers "what technology produced this log"; severity lives in the log **Level**.
* **The LWC `CATEGORY` export lost `WARNING`, `DEBUG`, and `EVENT`.** `import { CATEGORY } from 'c/triton'` now yields only `LWC` and `AURA`. JavaScript isn't compiled, so a removed key silently evaluates to `undefined` rather than erroring — the log still lands, re-categorized, with a note in Details.

`Log_Level__mdt` rules keyed on a removed category stop matching. See [Category is now origin-only](#category-is-now-origin-only) and [Upgrade notes](#upgrade-notes).
{% endhint %}

## Highlights

* **New static, builder-first API.** `Triton` is now called statically — no more `Triton.instance`. Logs are composed with a builder and passed to level methods: `Triton.info(...)`, `Triton.error(...)`, `Triton.logNow(...)`. A `Triton.t` shorthand builds from the active template. See [The new logging API](#the-new-logging-api).
* **Distributed tracing with spans.** New `spanId()` / `parentSpanId()` builder methods and `Span_Id__c` / `Parent_Span_Id__c` fields let you correlate work across transactions and services, alongside the existing `traceable()` / `tracePoint()` traceability controls.
* **Performance marks in Apex.** New `Triton.startMark()` / `Triton.endMark()` time a stretch of Apex, emit a paired set of `Performance` records, and parent every log created inside the mark to it — with the mark name derived from the stack trace, so call sites carry no hand-typed strings. `startTriggerMark()` adds the trigger context a stack trace can't supply. See [Performance marks in Apex](#performance-marks-in-apex).
* **Platform-cache transaction management.** A new `TritonCacheManager` centralizes Org cache operations, and `Triton.withCache()` ties a transaction id across LWC / Apex / Flow hops using the platform cache.
* **Deferred flushing to conserve platform events.** New `Triton.drain()` and `Triton.flushTop()`, plus a `TritonFlowFlushBatch`, let Flow and high-volume logging hand work off for batched publishing instead of consuming platform-event allocations inline.
* **Org-level runtime configuration.** A new `TritonSettings__c` custom setting adds a master **Disable Logging** kill switch, a **Disable Flow Flush Batch** switch, and **Cache TTL** / **Flow Flush Batch Delay** knobs, while `Log_Level__mdt` gains **Component** and **User** matching with most-specific-rule-wins resolution — all editable from Pharos Admin. See [Runtime configuration & admin controls](#runtime-configuration--admin-controls).
* **Level and convenience builder methods.** `TritonBuilder` gains `.error()`, `.warning()`, `.info()`, `.debug()`, `.fine()`, `.finer()`, `.finest()` (with optional summary overloads), plus `.exception(e)`, `.dmlResults(...)`, `.apexExecutionContext()`, and `.operation(...)`.
* **Flow version capture.** The Flow **Log** action gains a **Flow Version** input and `TritonBuilder` gains `.flowVersion(Decimal)`, writing `pharos__Flow_Version__c` — so a Flow log carries the plain API name and its version separately, the way native Flow error logs do. See [Flow version capture](#flow-version-capture).
* **Log-level rule evaluation fixed.** User-scoped rules now match for Apex logs, blank rule fields are true wildcards, and a malformed rule or an unparsable level can no longer break a transaction or drop a log. See [Log-level rule fixes](#log-level-rule-fixes) — one of these changes can also widen rules you already have, so read the [Upgrade notes](#upgrade-notes) too.
* **Overhauled test infrastructure.** A new `TritonTestFactory`, an expanded `TritonTest`, and a new `TritonFlowTest` bring comprehensive coverage of the static API, caching, and tracing.
* **Instrumentation skills + OpenTelemetry readiness.** Eight agent-agnostic instrumentation skills take a codebase from audit → migrate → instrument → guard: read-only coverage reports, 1.x → 2.0 conversion, per-technology instrumentation for Apex, LWC, and Flows, org-wide orchestration in reviewed batches, CI guardrails, and a persona that runs the whole catalog — each with a human-in-the-loop diff review and an optional deploy. Combined with the new spans/tracing model, this makes your org **OTEL-ready**: Triton's trace/span/parent-span concepts map cleanly onto OpenTelemetry. See [Instrumentation skills (companion tooling)](#instrumentation-skills-companion-tooling) and [OpenTelemetry readiness](#opentelemetry-readiness).

## The new logging API

The most significant change in 2.0 is the move from a singleton instance to a **static, builder-first** API.

**Before (1.42 and earlier):**

```apex
try {
    // ...
} catch (Exception e) {
    Triton.instance.error(TritonTypes.Area.Opportunities, e);
}
```

**After (2.0):**

```apex
try {
    // ...
} catch (Exception e) {
    Triton.error(Triton.t.area(TritonTypes.Area.Opportunities).exception(e));
}
```

### Static log methods

`Triton` exposes buffered and immediate log methods, all of which accept a `TritonBuilder`:

| Method                                                        | Behavior                         | Level                              |
| ------------------------------------------------------------- | -------------------------------- | ---------------------------------- |
| `Triton.log(builder)`                                         | Buffer the log                   | uses builder's level               |
| `Triton.error(builder)`                                       | Buffer the log                   | overrides to ERROR                 |
| `Triton.warning(builder)`                                     | Buffer the log                   | overrides to WARNING               |
| `Triton.info(builder)`                                        | Buffer the log                   | overrides to INFO                  |
| `Triton.debug(builder)`                                       | Buffer the log                   | overrides to DEBUG                 |
| `Triton.fine(builder)` / `finer(builder)` / `finest(builder)` | Buffer the log                   | overrides to FINE / FINER / FINEST |
| `Triton.logNow(builder)`                                      | Buffer **and** flush immediately | uses builder's level               |

Buffered logs are persisted when you call `Triton.flush()` (all logs), `Triton.flushTop()` (only the most recent), or `Triton.drain()` (hand the buffer off without publishing).

### Templates and the `Triton.t` shorthand

Set a reusable template once, then start each log from it. `Triton.t` is shorthand for `Triton.fromTemplate()` and automatically carries the active transaction id:

```apex
Triton.setTemplate(Triton.makeBuilder()
    .category(TritonTypes.Category.Apex)
    .area(TritonTypes.Area.Opportunities));

Triton.info(Triton.t.summary('Fetching items').details('...'));
Triton.error(Triton.t.summary('Failed').exception(e));
Triton.logNow(Triton.t.summary('Critical').error().exception(e));
```

### Convenience builder methods

`TritonBuilder` now carries the level and common configuration itself:

* **Level methods** — `.error()`, `.warning()`, `.info()`, `.debug()`, `.fine()`, `.finer()`, `.finest()`, each with an optional `(String summary)` overload.
* **`.exception(Exception e)`** — one call sets the type (from the exception type, or `Backend`), ERROR level, summary, details, stack trace, and flags issue creation.
* **`.dmlResults(List<Object>)`** — formats `Database.SaveResult`/`DeleteResult`/`UpsertResult`/`UndeleteResult`/`MergeResult` collections, capturing failed ids and error messages.
* **`.apexExecutionContext()` / `.operation(String)`** — capture the current execution context and label the operation.

## Category is now origin-only

`TritonTypes.Category` answers exactly one question — **what technology produced this log** — and the enum is now exactly:

`Apex` · `Flow` · `LWC` · `Aura`

`Integration`, `Warning`, `Event`, and `Debug` are gone. `Warning`, `Event`, and `Debug` described *severity*, which is what the log **Level** is for. `Integration` described a *kind of work*, which is what the Type and the attached payload are for.

**Logging a callout.** Keep the default `Apex` category and identify the log by its call type and payload:

```apex
HttpResponse response = new Http().send(request);

Triton.info(Triton.t
    .type(TritonTypes.Type.BackendCall)
    .integrationPayload(request, response)
    .summary('Loan application pushed'));
```

An integration log is an ordinary Apex log with the HTTP exchange attached — see [Integration Logs](/common-apex-usage-patters/integration-logs.md).

**Deployed Flows and LWCs keep logging.** A Flow **Log** action or an LWC that still passes a removed category string is not dropped. The entry is re-categorized to `Flow` or `LWC`, and a diagnostic note is appended to **Details**. The two tiers word it differently, so search for both:

| Source | Note appended to Details                                                      |
| ------ | ----------------------------------------------------------------------------- |
| Flow   | `Unable to locate category: Integration. Default Flow category will be used.` |
| LWC    | `Invalid Log Category: Integration. Default LWC category will be used.`       |

Treat those notes as a to-do list: they name precisely which Flows and components still pass a category that no longer exists.

**Apex gets no such grace.** Triton ships as source, so Apex referencing a removed enum member fails to compile when you deploy 2.0.

**Historical logs are untouched.** Category is stored as a string on the log record, so logs written before the upgrade keep whatever they were written with.

**`Log_Level__mdt` rules need re-keying.** A rule keyed on `Integration`, `Warning`, `Event`, or `Debug` does **not** error — the Category field is free text, so the rule simply stops matching anything and that slice of logs falls back to whatever broader rule applies. Re-key those rules to a real origin category, or to a Level. See [Upgrade notes](#upgrade-notes).

## Distributed tracing with spans

2.0 adds span-based tracing on top of Triton's existing transaction id and traceability:

* **`spanId(String)`** — set the current span identifier (stored on `Span_Id__c`).
* **`parentSpanId(String)`** — link to a parent span (stored on `Parent_Span_Id__c`).
* **`traceable(Boolean)`** — mark a builder as traceable (also flags issue creation).
* **`tracePoint()`** — emit a trace point when the builder is traceable.

The corresponding LWC layer (`TritonLwc` / `triton.js`) was updated to carry span and parent-span identifiers so front-end interactions can participate in the same trace. This aligns Triton's model with distributed-tracing conventions — see the [Spans & Distributed Tracing](/end-to-end/spans-and-tracing.md) guide for how these map to spans and traces.

## Performance marks in Apex

Apex reaches parity with the LWC performance marks. A **mark** times a stretch of code, emits a paired set of records, and builds a span tree as it goes:

```apex
Triton.startMark();          // name derived from the stack trace
try {
    recalculateForecast();
} finally {
    Triton.endMark();
}
```

| Method                      | Behavior                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------- |
| `Triton.startMark()`        | Open a mark named `<Class>.<method>`, derived from the stack trace. Returns the name. |
| `Triton.startMark(String)`  | Open a mark with an explicit name, for names that aren't method names.                |
| `Triton.endMark()`          | Close the innermost open mark.                                                        |
| `Triton.endMark(String)`    | Close the newest mark with that name, for out-of-order completion.                    |
| `Triton.clearMarks()`       | Discard all open marks and reset to the request root.                                 |
| `Triton.startTriggerMark()` | Open a mark named `<Handler>.<Trigger.operationType>`. Zero arguments.                |

* **Paired records.** A mark publishes `Performance started: <mark>` when it opens and `Performance: <mark>` with `Duration: <n>ms` when it closes, both on the same span — the same prefixes the LWC marks use, so both tiers render identically.
* **Ambient parenting.** Marks nest LIFO, and an ordinary log emitted inside a mark becomes a child of it instead of a flat sibling. The outermost mark parents to the transaction span. Ids you set with `.spanId(...)` / `.parentSpanId(...)` are never overwritten.
* **Starting a transaction no longer destroys an open mark.** `Triton.startTransaction()` resets the span hierarchy only when no mark is open. An LWC log save or a Flow **Log** action firing inside an open Apex mark used to discard that mark along with its unwritten completion record; now the mark survives and closes normally. `Triton.clearMarks()` remains the explicit way to reset.
* **Suppression is transparent.** If a `Log_Level__mdt` rule filters out a mark's opening record, its children re-parent to the nearest surviving ancestor rather than being orphaned.
* **Buffered, always `FINE`.** No mark method flushes, and there is no `level` argument — verbosity stays an admin's `Log_Level__mdt` decision rather than a constant baked into a call site.
* **Batch and async need no new API.** `Triton.resumeTransaction(carriedId)` (if you carry one) followed by `Triton.startMark()` is the whole idiom; each chunk begins as its own subtree under the shared transaction.

{% hint style="warning" %}
**Marks are opt-in, and they reshape span fields once used.** Code that never calls `startMark` produces exactly the span fields it does today. Inside an open mark, `Span_Id__c` on an ordinary log becomes a fresh UUID rather than the request id — move any query, report, or dashboard that used `Span_Id__c == requestId` to mean "belongs to request X" onto **`pharos__Request_Id_External__c`**, which is where that fact actually lives. `Parent_Span_Id__c` also goes from always-null to populated, and each mark adds two records to the log volume. A mark filtered out by log levels is still an *open* mark, so it still reshapes the logs around it.
{% endhint %}

Full guide: [Performance Marks in Apex](/common-apex-usage-patters/performance-marks.md).

## Flow version capture

Flow logs now record the flow's version in a field of its own instead of folding it into the name — the same split native Flow error logs use:

* **Flow → Log action:** a new optional **Flow Version** input (`flowVersion`), alongside the existing **Flow API Name**. Flow passes it as a string.
* **Apex:** `TritonBuilder.flowVersion(Decimal)`, written to `pharos__Flow_Version__c`. The managed field is Text, so the number is stored in its canonical string form.

A value Triton can't parse never fails the log: it appends `Unable to parse flow version: <value>. Flow Version will not be set.` to **Details** and saves the log without the version.

{% hint style="warning" %}
**Manual step for already-instrumented Flows: `flowApiName` must be the plain API name.** Component-scoped `Log_Level__mdt` rules match the Flow API Name **verbatim**, so a version-suffixed value like `My_Flow-4` will not match a rule naming `My_Flow`. Put the plain name in **Flow API Name** and the number in **Flow Version**. See [Upgrade notes](#upgrade-notes).
{% endhint %}

## OpenTelemetry readiness

The tracing model above isn't just useful inside Pharos — it's what makes a 2.0 org **OpenTelemetry-ready**. Triton's three correlation primitives line up directly with OTel's tracing vocabulary:

| OpenTelemetry concept | Triton 2.0 equivalent                                                                                            |
| --------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Trace**             | The transaction id (`Triton.TRANSACTION_ID` / `this.triton.transactionId`) correlating one end-to-end operation. |
| **Span**              | `Span_Id__c`, set via `.spanId(...)`.                                                                            |
| **Parent span**       | `Parent_Span_Id__c`, set via `.parentSpanId(...)`.                                                               |

Because the concepts map cleanly, instrumented Triton logs carry the structure an observability stack expects, and they're a solid foundation for a future OpenTelemetry integration.

{% hint style="warning" %}
**Readiness, not a shipped exporter.** Triton 2.0 is **not** an OpenTelemetry SDK and does **not** emit OTLP — there is no OTEL exporter in the package. "OTEL-ready" means the data model aligns with OpenTelemetry's, giving you a clean path toward one. Treat trace / span / parent span as an accurate mental model, not a drop-in pipeline. Full mapping: [Spans & Distributed Tracing → How this maps to OpenTelemetry](/end-to-end/spans-and-tracing.md#how-this-maps-to-opentelemetry).
{% endhint %}

## Instrumentation skills (companion tooling)

2.0 is accompanied by a set of **agent-agnostic instrumentation skills** — portable playbooks surfaced as `/pharos:` slash commands in agents that support them (and runnable from any coding agent) — that automate adding Triton instrumentation to existing code, following the 2.0 static API and Triton best practices:

**Instrument one technology:**

* **`/pharos:instrument-apex`** — instruments Apex classes and triggers: structure-aware resume-first transactions, `logNow` + rethrow in catches, `.dmlResults(...)` on partial DML, callout `integrationPayload`, `System.debug` → `Triton.debug`, and flush at the right boundaries.
* **`/pharos:instrument-lwc`** — adds the `c/triton` logger, `bindToComponent`, and `timeBackendCall` (which carries span context), plus transaction stitching to Apex.
* **`/pharos:instrument-flows`** — inserts `TritonFlow` "Log" actions with individual named inputs, instrumenting flow entry and every fault path.

**Work across a codebase:**

* **`/pharos:audit-logging`** — a read-only scan that reports logging gaps and a prioritized instrumentation plan. Writes no source files; run it first for the map.
* **`/pharos:migrate-to-2.0`** — converts 1.x `Triton.instance` code to the 2.0 static API, stopping to ask on every call where a rename would silently change immediate-vs-buffered publishing.
* **`/pharos:instrument-org`** — orchestrates the three per-technology skills across a whole project: consistent Areas per feature, transaction stitching across LWC → Apex → Flow, and a review checkpoint every batch.
* **`/pharos:setup-standards`** — scaffolds the guardrails that keep an instrumented codebase instrumented: CI checks for `System.debug` and `Triton.instance`, a PR review checklist, and `Log_Level__mdt` defaults.
* **`/pharos:instrumentation-agent`** — the persona that ties the catalog together and runs the full audit → migrate → instrument → guard sequence end to end, pausing for you between stages.

Each skill runs a **human-in-the-loop** loop — prerequisites check → analyze → apply → **diff review (yes / modify / skip)** → write → optional `--deploy` via the Salesforce CLI — so nothing is written until you approve it. Run them repeatedly (or loop an agent) across a backlog to roll out consistent instrumentation quickly.

{% hint style="info" %}
**Companion tooling — ships alongside 2.0, not inside the managed package.** The skills live in the Triton repository's `agent-skills/` directory on the 2.0 line. They are **not** part of the core Triton managed package and are never deployed into an org — they're developer tooling you run from your coding agent against your local project, and the code they generate targets Triton 2.0. See the [Instrumentation Skills](/instrumentation-skills.md) section for full usage.
{% endhint %}

## Platform-cache transaction management

A new **`TritonCacheManager`** centralizes platform cache access (Org cache). Transaction handling is now cache-aware:

* **`Triton.withCache()`** — enable platform cache for transaction management; resumes an existing cached transaction id if present, otherwise starts a new one. Falls back gracefully when cache is unavailable.
* **`Triton.startTransaction()` / `resumeTransaction(id)` / `stopTransaction()`** — now cache the transaction id (and extend its TTL) when caching is enabled, making it easier to correlate logs across LWC / Apex / Flow transitions.

## Deferred flushing & Flow batching

To reduce platform-event (PE) consumption under load, 2.0 introduces deferred flushing:

* **`Triton.drain()`** — returns all buffered logs and clears the buffer without publishing, so a caller (such as Flow) can hand them off for batched processing.
* **`Triton.flushTop()`** — publishes only the most recent buffered log immediately.
* **`TritonFlowFlushBatch`** — a batch class that flushes Flow-originated logs, integrating with the cache manager so Flow logging consumes fewer PE allocations.

`flush()` and `flushTop()` continue to schedule delayed publishing with exponential backoff when PE allocation is near capacity (95%).

## Runtime configuration & admin controls

2.0 moves logging behavior under org-level runtime configuration — a new `TritonSettings__c` custom setting plus richer `Log_Level__mdt` matching — all editable from the **Triton** area of Pharos Admin (Configuration → **Settings** / **Log Levels**) without a code change or redeploy.

**Global switches (`TritonSettings__c`):**

* **Disable Logging** — a master kill switch. When set, all Triton logging (Apex, Flow, LWC) is short-circuited before logs are built or persisted.
* **Disable Flow Flush Batch** — when set, Flow logs flush immediately (synchronously, in-transaction) instead of via the delayed `TritonFlowFlushBatch`. No logs are lost either way.

**Operational knobs (`TritonSettings__c`):**

* **Cache TTL (minutes)** — lifetime of Triton's Org-cache entries (the transaction id and buffered Flow logs). Blank uses the packaged default (5 minutes).
* **Flow Flush Batch Delay (minutes)** — how long the flush batch waits before it runs. Blank uses the packaged default (1 minute).

**Log-level matching (`Log_Level__mdt`):**

* Rules now match on **Component** and **User** in addition to Category / Type / Area, so you can scope verbosity down to a single class, LWC, Flow, or user.
* When several rules target a log, the **most specific** one wins and overrides the others in *both* directions — a narrow rule can raise *or* lower verbosity for its slice. Specificity is ranked User > Component > Area > Type > Category. See [Log Levels](/core-concepts/log-levels.md).

**Transaction-id cache moved to Org cache.** Transaction correlation now uses Salesforce **Org** cache instead of Session cache, so a cached transaction id survives across asynchronous and session-less contexts (Batch, Queueable, Scheduled, `@future`). See [Transactions & Platform Cache](/end-to-end/transactions-and-cache.md).

### Log-level rule fixes

Four fixes to how `Log_Level__mdt` rules are evaluated. All four make rules behave the way the field labels already promised:

* **User-scoped rules now actually match.** `build()` stamps `pharos__User_Id__c` with `UserInfo.getUserId()` when nothing else set it, so a **User** rule is evaluated against a real user id at filter time. Previously a User rule was inert for Apex logs: the id was filled in at flush time, after filtering had already run.
* **Blank rule fields are true wildcards.** A field holding `''` or whitespace now behaves exactly like an empty one. Previously only a genuine `null` was a wildcard, so a rule saved with a blank-looking field could silently match nothing.
* **Matching on Category, Type, Area, and Component is case-insensitive.** These are free-text fields typed by hand, so `apex` and `Apex` now mean the same thing. **User Id stays case-sensitive**, compared on its 15-character form — Salesforce Ids are case-sensitive base-62, and folding their case would collide distinct users. This change can widen rules you already have; see [Upgrade notes](#upgrade-notes).
* **A bad rule can no longer break a transaction.** A malformed `Log_Level__mdt` row — one with a blank **Level**, for instance — is skipped instead of throwing into the host transaction, and a log whose own level is unparsable is kept rather than dropped. Filtering fails open: when Triton can't decide, the log survives.

See [Log Levels](/core-concepts/log-levels.md) for how rules are scoped and resolved.

## Governor-limit tracking

Limit tracking is **unchanged from 1.42**. `limitInfo()` captures the full set — SOQL queries and rows, DML statements and rows, CPU time, heap size, callouts, SOSL queries, aggregate queries, `@future` calls, queueable jobs, email invocations, push notifications, async (batch) calls, and immediate-publish DML — each alongside its governor ceiling. `limitInfoLite()` captures the essential subset, and `withFullLimitInfo()` / `withoutLimitInfo()` toggle between them.

{% hint style="warning" %}
**Four limit fields ship unpopulated.** `Callout_Time__c`, `SOQL_Rows_Retrieved__c`, `Scheduled_Jobs__c`, and `Script_Statements__c` exist on `pharos__Log__c`, but **nothing writes them in 2.0** — no builder method populates them, and the Log Limits component neither reads nor renders them. They also have no companion `_Limit__c` field, so there is no ceiling to measure them against. Expect them to be blank on every log, and don't build reports, dashboards, or alerts on them.
{% endhint %}

## Permission set updates

`Triton_Read` and `Triton_Write` were updated to include the new span fields — `Span_Id__c` and `Parent_Span_Id__c`. Both sets also grant Apex class access to `LogLimitsController`, which the Log Limits component on the Log record page needs.

After deploying 2.0, **re-assign the permission sets** (or refresh them through your deployment pipeline) so users pick up the new field-level access — and if you deploy the sets separately from the rest of 2.0, deploy them **after** the span fields, never before. Both steps are load-bearing: see [Upgrade notes](#upgrade-notes) for what breaks if you skip either, and [Configure Permissions](/installing-pharos-triton.md#configure-permissions) for what each set grants.

## Test suite

The Apex test infrastructure was substantially reworked:

* **`TritonTestFactory`** — a new factory for constructing test logs and builders against the static API.
* **`TritonTest`** — significantly expanded to cover static access, caching, spans, and deferred flushing.
* **`TritonFlowTest`** — a new test class covering Flow logging and the flush batch.

## Upgrade notes

* **Breaking: the singleton is gone.** Replace all `Triton.instance.<method>(...)` calls with the static, builder-first equivalents. For example, `Triton.instance.error(area, e)` becomes `Triton.error(Triton.t.area(area).exception(e))` (or set the area via a template).
* **Breaking: categories are origin-only.** `TritonTypes.Category` is now exactly `Apex` · `Flow` · `LWC` · `Aura`; `Integration`, `Warning`, `Event`, and `Debug` are removed, and Apex referencing them won't compile. Also **re-key any `Log_Level__mdt` rule that named a removed category** — and note that a stale rule gives you no warning: the Category field is plain text, not a picklist, so the rule doesn't error, it just silently stops matching and that slice of logs reverts to whatever broader rule applies. Deployed Flows and LWCs keep logging with a diagnostic note in Details; historical logs keep their stored category strings. Full detail: [Category is now origin-only](#category-is-now-origin-only).
* **Fix up `flowApiName` on already-instrumented Flows.** Component-scoped log levels match the Flow API Name verbatim, so a version-suffixed value such as `My_Flow-4` will not match a rule naming `My_Flow`. Move the version into the new **Flow Version** input and leave the plain API name behind. See [Flow version capture](#flow-version-capture).
* **Case-insensitive rule matching can widen rules you already have.** Category / Type / Area / Component now match case-insensitively, so a rule field typed `apex` — which previously matched nothing, because the log's category is `Apex` — now matches every Apex log. Review your existing `Log_Level__mdt` rows for values whose case doesn't match what they were meant to target: a rule that had quietly never fired may start firing after the upgrade.
* **Buffered vs immediate naming changed.** The old `add*` (buffer) / bare (instant) method pairs are replaced by `Triton.log(...)` / level methods (buffer) and `Triton.logNow(...)` (immediate). Audit any code that relied on the instantaneous default methods to ensure logs are still flushed when expected.
* **Upgrade Triton&#x20;*****before*****&#x20;you deploy the Triton permission sets.** `Triton_Read` and `Triton_Write` grant `Span_Id__c` and `Parent_Span_Id__c` — fields Triton adds to `pharos__Log__c` itself — so deploying the sets into an org still on an older Triton fails with an invalid-field error. This error depends on your Triton version, not your Pharos package version. Upgrade Triton first, then deploy the permission sets, then re-assign them; a single deploy of the whole 2.0 source works too, because the fields and the sets land together. If you've already hit the error, finish the Triton upgrade and deploy again — nothing needs undoing.
* **Re-assign the permission sets — and know what a missing `Triton_Write` costs.** Re-assignment isn't cosmetic. Triton publishes a platform event, and a separate subscriber process performs the `pharos__Log__c` insert; if that subscriber's running user lacks `Triton_Write`, **logs still land — silently missing their limit and span data, with no error raised anywhere.** So **empty limit fields on new logs are a permission-set symptom first**: check the subscriber's permission sets before you go looking at governor limits. See [Configure Permissions](/installing-pharos-triton.md#configure-permissions).
* **Platform cache is opt-in.** Call `Triton.withCache()` to enable cache-backed transaction management; without it, behavior falls back to the standard transaction id handling.
* **Tracing is additive.** `spanId()`, `parentSpanId()`, `traceable()`, and `tracePoint()` are optional; existing logs continue to work without them.
* **Marks are opt-in, but they reshape span fields once used** — including `Span_Id__c` on ordinary logs inside a mark. See [Performance marks in Apex](#performance-marks-in-apex) before adopting them.
