pinecallDocs
The gateway / Projections

Projections

What a call's log reduces to, and who is allowed to read each field.

A call's log holds everything. What a reader receives is a projection of it, chosen by who is reading, and there are exactly two: tenant and public. This is a public contract: a console, a widget or a customer's own reader may rely on every row below. The runtime writes the table once, in log/readers.py.

the reader knocks with projection
an org's key (the tenant's process, the console, the CLI) tenant
a token bound to one call (a browser, a page's log_token) public, or what the mint asked (log: tenant)

Tenant: everything, the declared PII masked#

Every entry and every field, with what the agent declared private masked: a state field declared pii reads as *** in state.changed, in call.attached and in the reduced state, and an argument a tool lists under pii reads as *** in tool.call. The key stays, so a reader knows a value exists; the value goes whole, so nothing leaks through its type. The mask is applied when the entry is written, so the log holds it masked and every reader reads it so; this projection masks again, by the reader's declaration, what a log written before that holds in clear (../security/private-values.md). The app's own socket is not a reader of this table: it runs the tools and holds the state, and is sent them as they were written. Transcripts and metrics are never touched.

Public: a whitelist, row by row#

A participant learns nothing beyond their own call: never agent, never call in an envelope, never a cost, never a vendor. The envelope keeps seq, ts, type, ephemeral; an entry type absent from the table is dropped whole, and a field absent from a row is dropped.

entry fields kept
call.ringing · call.dialing channel
call.started channel direction started_at
call.ended reason ended_by ended_at duration_s
user.state · agent.state state
user.transcript text final
agent.transcript speech_id text final: one delta, never the reply so far
turn.user speech_id text
turn.agent speech_id text interrupted metrics, of the metrics e2e_latency alone
room.opened name sid
participant.joined identity kind name
participant.left identity
participant.speaking identity speaking
confirm.request phrase ttl_s
confirm.granted · confirm.declined none: the type is the verdict
call.transferred to mode ok error
call.line held
event.received name data source identity, only for the viewer who sent it
state.changed state changed, filtered to the fields declared public, never its cause
log.gap from_seq to_seq snapshot
log.caught_up seq

An entry of those three the projection reads into (turn.agent, state.changed, log.gap) that does not read in today's shape is withheld from the public, since what it would leak cannot be told. The reduced state a public reader gets keeps seq, status, user_state, agent_state, live, turns (role, speech_id, text, interrupted; an agent turn's e2e_latency), app_state (the public fields), room (identity, kind, name, joined_at, speaking), confirms (phrase, status), transfer, held and the viewer's own events.

No third projection, no per-field opt-in beyond the three visibilities an agent declares on its state fields (public, tenant, pii), no masking of transcripts.