Skip to content
v0.15.2Source-only (not yet published)OTel compatible

Observability & OpenTelemetry

PEAC records are signed proofs — OpenTelemetry spans are operational signals. They are complementary, not competing. @peac/telemetry-otel bridges the two: emit a PEAC record, attach its reference hash to the corresponding OTel span.

peac.record.ref is a custom PEAC span attribute. It is not an OpenTelemetry semantic convention unless accepted by the OTel project.

OpenTelemetry span

Operational signal. What happened, how long it took, whether it errored. Visible to your team inside your observability stack.

  • Trace context propagation
  • Latency, error rate, throughput
  • Collector, Jaeger, Grafana, Datadog
  • Mutable — spans are sampled, dropped, overwritten

PEAC signed record

Cryptographic proof. What terms applied, what was agreed to. Verifiable offline by anyone with the issuer's public key.

  • Ed25519 signed compact JWS
  • Policy binding, consent, payment evidence
  • Carries across organizational boundaries
  • Immutable — signed once, verifiable forever

Install

The published telemetry hook interface is @peac/telemetry. The OpenTelemetry bridge @peac/telemetry-otel is currently source-only (build it from the repository); it is not yet published to npm.

pnpm add @peac/telemetry @peac/protocol
# Bring your own OTel SDK; PEAC core has zero OTel dependency
pnpm add @opentelemetry/api @opentelemetry/sdk-node

Attach a record reference to a span

@peac/telemetry-otel is source-only in v0.15.0 (not published to npm); the import below is for source-repository consumers. Create the OpenTelemetry provider once at startup, then pass it as thetelemetry hook when you issue or verify records. Each operation emits a span carrying the peac.record.ref attribute, so any party with access to the span can retrieve and verify the corresponding signed record independently.

import { issue } from '@peac/protocol';
import { generateKeypair } from '@peac/crypto';
import { createOtelProvider } from '@peac/telemetry-otel';

// Create the OTel telemetry provider once at startup
const telemetry = createOtelProvider({
  privacyMode: 'strict', // 'strict' | 'balanced' | 'custom'
  hashSalt: process.env.PEAC_TELEMETRY_SALT,
});

const { privateKey } = await generateKeypair();

// Passing the provider as the telemetry hook emits an OTel span
// carrying the peac.record.ref attribute when the record is issued.
const { jws } = await issue({
  iss: 'https://api.example.com',
  kind: 'evidence',
  type: 'org.peacprotocol/access',
  pillars: ['access'],
  extensions: {
    'org.peacprotocol/access': {
      resource: 'urn:tool:my-tool',
      action: 'invoke',
      decision: 'allow',
    },
  },
  privateKey,
  kid: 'peac-2026-03',
  telemetry,
});

The peac.record.ref attribute

peac.record.ref is a PEAC-defined custom span attribute. Its value is the SHA-256 hex digest of the compact JWS — a stable, collision-resistant pointer to the signed record.

Span attribute on the emitted record

{
  "peac.record.ref": "sha256:7f83b1657ff1..."
}

Verify the record later

import { verifyLocal } from '@peac/protocol';

// Retrieve jws by ref from your store
const jws = await receipts.getByRef(ref);

const result = await verifyLocal(jws, publicKey, {
  issuer: 'https://api.example.com',
});
// result.valid === true
// result.claims.pillars = ['access']

Naming note: peac.record.ref follows OTel dotted naming convention but is not an official OTel semantic convention. PEAC may propose this for standardization in the future. Do not reference it as an OTel convention in your own docs.

Privacy modes

Not all PEAC record data should land in your telemetry pipeline. The telemetry bridge supports three privacy modes to control what gets exported alongside spans.

strictDefault

Strict (default)

Hash all identifiers before they reach telemetry. The safe default for cross-organizational spans.

balanced

Balanced

Hash identifiers but include non-sensitive operational fields such as rail and amount for richer insight.

custom

Custom

Provide an explicit allowlist of fields that may be exported. For advanced, audited pipelines.

import { createOtelProvider } from '@peac/telemetry-otel';

// Strict (default): hash all identifiers
const strict = createOtelProvider({ privacyMode: 'strict' });

// Balanced: hash identifiers, include non-sensitive operational fields
const balanced = createOtelProvider({ privacyMode: 'balanced' });

// Custom: explicit allowlist of exportable fields
const custom = createOtelProvider({ privacyMode: 'custom' });

// Pass the chosen provider as the telemetry hook on issue() / verifyLocal()

Lifecycle records and OTel

Lifecycle observation records (emitted by peac emit lifecycle or @peac/schema validateLifecycleObservation()) can also be bridged to OTel spans. This is informative — PEAC does not orchestrate workflows or assign tasks. It records what your orchestrator reported.

import { validateLifecycleObservation } from '@peac/schema';

// Validate the lifecycle observation shape before issuing
const result = validateLifecycleObservation({
  event_kind: 'lifecycle-approval-granted',
  approval_ref: 'urn:approval:2026-06-01-abc123',
  approver_ref: 'ref:approver:agent-governance',
  observed_at: new Date().toISOString(),
});

if (!result.success) throw new Error('invalid lifecycle observation');

// Issue the record with a telemetry provider (see above) so the emitted
// OTel span carries peac.record.ref. The canonical CLI path is:
//   peac emit lifecycle --event-kind lifecycle-approval-granted ...

The lifecycle-observation OTel bridge is informative-only per the PEAC spec. PEAC may emit peac.record.ref alongside OTel spans; it does not ship an OTel exporter, SDK dependency, collector, or semantic-convention claim.

Zero OTel dependency in PEAC core

@peac/kernel, @peac/schema,@peac/crypto, and @peac/protocolhave no dependency on @opentelemetry/api or any OTel package. The bridge lives exclusively in @peac/telemetry-otel.

This means you can use PEAC signing and verification in edge environments, serverless functions, and embedded runtimes that cannot carry the OTel SDK — without any change to your core record flow.

Resources

Add verifiable record refs to your OTel pipeline

One attribute, zero OTel vendor lock-in. Any OTel-compatible backend can store and query peac.record.ref.