polyc-facts 2026.10.2

Shared semantic-fold library: decode-to-fact functions reused by every consumer that reads the event log, so a payment receipt or a tool call means the same thing everywhere it's read.
//! Shared semantic-fold library: pure decode-to-fact functions.
//!
//! The event log already has one canonical bytes→struct decode path
//! ([`polyc_proto::events_decode`]); what was missing was one canonical
//! **fold** on top of an already-decoded struct — the step that turns a
//! decoded wire message or a signed receipt into a fact with meaning ("this
//! is a tool call", "this receipt is verified"). Before this crate existed,
//! that fold was reimplemented independently by every consumer that wanted
//! it, and the reimplementations could (and did) disagree, including a
//! payment-receipt divergence this crate closes.
//!
//! This crate owns exactly the fold: mechanical, keyed by a stable id,
//! wanted in the same shape by two or more consumers. It has no I/O and no
//! async: every function here is a pure, synchronous transform so a caller
//! that can never fail open (a spend gate, a budget check) can call it
//! directly without taking on a query engine's availability as a dependency.
//!
//! Presentation stays with the consumer: truncation, snippet windowing,
//! ordering, and size budgets are per-surface shaping this crate never does.
//! Facts come out whole.
//!
//! # Two amendments to that charter, and why
//!
//! **Folds may decode.** An earlier wording said callers pass already-decoded
//! structs. That was never true of the code — most entry points here take a
//! raw `payload: &[u8]` or an `&Event` and decode internally, and
//! [`attribution_events_with_positions`] takes positioned events — so the
//! sentence described an intention the crate had already outgrown. Decoding
//! belongs with the fold that gives the bytes meaning.
//!
//! **One projection lives here: commit scoping.** [`committed_turn_ids`] and
//! [`committed_message_facts`] decide which turns are searchable and extract
//! their text. That is a policy, and [`attribution_events`]' own doc still
//! correctly says commit scoping "stays with the caller" for ITS purposes,
//! where consumers legitimately disagree about what committed means. The
//! narrow exception is for consumers that must NOT disagree: history
//! navigation and participation-scoped search answer the same question, so a
//! second implementation is a bug waiting for the two to drift. See
//! [`committed_message_facts`]'s own doc for the boundary.

#![forbid(unsafe_code)]
#![deny(missing_docs)]

mod administrator_audit;
mod approvals;
mod attribution;
mod committed;
mod conversation_core;
mod conversation_trace;
mod delegation_history;
mod excision;
mod execution_history;
mod grant_replays;
mod handoffs;
mod incognito;
mod message_content;
mod model_call;
mod observed_routines;
mod persona_memory;
mod query_audit;
mod receipts;
mod refusals;
mod routine_lifecycle;
mod security_history;
mod subagent;
mod summary;
mod usage;
mod wallet_link_lifecycle;
mod withholding;

pub use administrator_audit::{
    AdminModelChangeFact, AdminSignatureStatus, AdministratorAuditError, AdministratorAuditFacts,
    fold_administrator_audit,
};
pub use approvals::{
    ApprovalFact, ApprovalRequestFact, ApprovalResponseFact, ApprovalSignatureStatus,
    classify_response_signature, fold_approval_event,
};
pub use attribution::{
    AttributionEventKind, AttributionFact, AttributionScope, attribution_events,
    attribution_events_with_positions, caller_by_turn_last_wins,
};
pub use committed::{CommittedMessageFact, committed_message_facts, committed_turn_ids};
pub use conversation_core::{
    CommittedTurnFact, ConversationCoreError, ConversationCoreFacts, ConversationMessageFact,
    LifecycleBarrier, PreparedSource, fold_conversation_core, lifecycle_barrier,
    prepare_conversation_core,
};
pub use conversation_trace::{
    ApprovalDecision, ApprovalPhase, AudienceView, BoundaryMarker, ConversationTraceError,
    ConversationTraceFacts, FailureKind, Id, MarkerKind, MessageRole, PaymentDirection,
    QuestionDecision, QuestionPhase, RoleVerdict, SignerRole, SubagentPhase, Text, ToolDocument,
    ToolStatus, TraceBoundsError, TraceFailureFact, TraceSignerFact, TraceStepFact,
    TraceStepPayload, TraceTrust, TraceTurnFact, TraceWarningFact, TurnStatus, WarningCode,
    bounded_hash, bounded_id_list, bounded_warning, fold_conversation_trace,
};
pub use delegation_history::{
    ConversationDelegationError, ConversationDelegationFacts, HandoffHistoryFact, HandoffPhase,
    HandoffSignerFact, fold_conversation_delegation, prepare_and_fold_conversation_delegation,
};
pub use excision::{
    EXCISED_KIND, excised_positions, excised_positions_by_marker, strip_excised,
    verified_excisions, verified_excisions_matching, verified_excisions_matching_positioned,
};
pub use execution_history::{
    ConversationExecutionError, ConversationExecutionFacts, ModelCallHistoryFact,
    SummaryHistoryFact, ToolHistoryFact, ToolHistoryKind, TurnDispatchHistoryFact,
    TurnFailureHistoryFact, UsageHistoryFact, fold_conversation_execution,
    prepare_and_fold_conversation_execution,
};
pub use grant_replays::{GrantReplayFact, GrantReplaySignatureStatus, fold_grant_replay_event};
pub use handoffs::{HandoffDeniedFact, HandoffFact, HandoffSpawnFact, fold_handoff_event};
pub use incognito::fold_incognito_set;
pub use message_content::{
    MessageContent, MessageContentFold, TextFact, ToolCallFact, ToolResultFact,
    fold_message_content,
};
pub use model_call::{DecodeParamsFact, ModelCallFact, fold_model_call_event};
pub use observed_routines::{
    DecodeError as ObservedRoutinesDecodeError, ObservedRoutineRow,
    decode_rows as decode_observed_routine_rows, encode_rows as encode_observed_routine_rows,
};
pub use persona_memory::{
    DURABILITY_DURABLE, DURABILITY_SESSION, DURABILITY_UNSPECIFIED, MemoryCorroborationRow,
    MemoryExtractionRow, MemoryFactRow, MemoryFenceRow, MemoryInvalidationRow, MemoryListRow,
    MemoryProfileRewriteRow, MemoryProvenanceIdentityRow, MemorySummaryRow, MemoryUnknownRow,
    PersonaMemoryFacts, PersonaMemoryFoldError, ProvenanceIdentity, ProvenanceTriple, SCOPE_DIRECT,
    SCOPE_PORTABLE, SCOPE_UNSPECIFIED, fold_persona_memory,
};
pub use query_audit::{
    QueryAuditCompletionFact, QueryAuditFacts, QueryAuditFoldError, QueryAuditIntentFact,
    QueryAuditSourcePinFact, fold_query_audit_history,
};
pub use receipts::{verified_outbound_receipts, verified_receipts};
pub use refusals::verified_refusals;
pub use routine_lifecycle::{
    RoutineFireFact, RoutineLifecycleError, RoutineLifecycleFact, RoutineLifecycleFacts,
    RoutineSetupFact, fold_routine_lifecycle,
};
pub use security_history::{
    ApprovalDetailFact, ApprovalHistoryFact, ApprovalOutcome, AttributionHistoryFact,
    AttributionProvenanceFact, ConversationSecurityError, ConversationSecurityFacts,
    GrantReplayHistoryFact, GrantReplaySignerFact, RoutineGrantMutationFact,
    fold_conversation_security, prepare_and_fold_conversation_security,
};
pub use subagent::{
    SubagentResultFact, SubagentSpawnFact, fold_subagent_model_call, fold_subagent_result,
    fold_subagent_spawn,
};
pub use summary::{
    SummaryFact, SummaryGateFact, fold_summary_event, fold_summary_gate_admitted_event,
    fold_summary_gate_rejected_event,
};
pub use usage::{UsageFact, fold_usage_event};
pub use wallet_link_lifecycle::verified_wallet_link_lifecycle_events;
pub use withholding::{
    withheld_turn_ids, withheld_turn_ids_positioned, withhold_paused_turn_text,
    withhold_paused_turn_text_positioned,
};