epiphany/crates/epiphany-textproj/src/lib.rs

164 lines
7.6 KiB
Rust

#![forbid(unsafe_code)]
//! The Epiphany Text Projection companion document layer.
//!
//! This crate bridges canonical bundle documents and the normative line-oriented
//! s-expression representation defined by `spec/text_projection.tex`.
pub mod parse;
pub mod project;
pub mod serialize;
pub mod vectors;
use epiphany_bundle::{
ChunkKind, DocumentId, ExtensionId, FrontierBytes, LineageId, ProfileDeclaration, ProfileId,
ReductionAlgorithmVersion, SchemaVersion, SemVer, SnapshotId,
};
use epiphany_ops::OperationEnvelope;
/// The one Text Projection companion version implemented by this crate.
///
/// A parser must reject every other version rather than migrating or
/// normalizing it on read.
///
/// Bumped 0.7.0 → 0.8.0 by the genesis tranche G1, which appended
/// `create-instrument` to the `kind` production — the first operation kind
/// added since the header was gated to a single version. Extending the grammar
/// without moving this constant would leave two incompatible grammars both
/// claiming `(0 7 0)`. Cached projections do not migrate: a `TextProjection`
/// chunk is a non-canonical accelerator, so a stale one is regenerated.
///
/// Bumped again 0.8.0 → 0.9.0 by the genesis tranche G2a, which appended
/// `set-canvas-layout-defaults` and `set-spelling-precedence` to the `kind`
/// production — the same reasoning: extending the grammar without moving this
/// constant would leave two incompatible grammars both claiming `(0 8 0)`.
///
/// Bumped again 0.9.0 → 0.10.0 by the G-minor rung
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md`), which added the carried manifest
/// [`SchemaVersion`] to the `document` production
/// (`document ::= "(document " bytes " " schema ")"`). **Not** because of
/// operation-block schema-minor stamping — block schemas are discarded during
/// projection and stay projection-invisible, exactly as before — but because
/// the manifest's aggregate `SchemaVersion` becomes a carried `TextDocument`
/// attribute that this companion cannot derive (`epiphany-textproj` has no
/// `epiphany-layout-ir` dependency and structurally cannot decode edit-barrier
/// bytes). Holding the version while changing the grammar would leave two
/// incompatible grammars both claiming `(0 9 0)`.
///
/// Bumped again 0.10.0 → 0.11.0 by the genesis tranche G2b
/// (`spec/CONTRACT_GENESIS_G2B_TUNING.md`), which appended
/// `set-tuning-context` to the `kind` production — the same reasoning as G1
/// and G2a: extending the grammar without moving this constant would leave
/// two incompatible grammars both claiming `(0 10 0)`.
///
/// Bumped again 0.11.0 → 0.12.0 by the genesis tranche G3a
/// (`spec/CONTRACT_GENESIS_G3A_ENTITIES.md`), which appended
/// `create-staff-group`, `create-part-definition`, `create-analysis-layer`,
/// and `create-view` to the `kind` production — the same reasoning as every
/// prior kind append: extending the grammar without moving this constant
/// would leave two incompatible grammars both claiming `(0 11 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 13, 0);
/// A parsed canonical Text Projection document.
///
/// Under `req:textproj:derive-or-carry`, this representation deliberately erases
/// physical offsets, compressed and uncompressed storage lengths, and
/// compression choices because serialization is free to choose a new physical
/// layout. It also omits the derivable `ChunkId`, `ContentHash`, and `BlobId`;
/// those identities are recomputed from kind, schema, and inline payload.
/// Finally, it drops the non-canonical `operation_index_root`,
/// `acceleration_snapshots`, `text_projection_root`, `integrity_root`, and
/// `operation_block_summaries` accelerators. None contributes to canonical
/// document semantics, so a bundle serialized from this form correctly rebuilds
/// or omits them rather than carrying stale physical metadata.
#[derive(Debug, PartialEq)]
pub struct TextDocument {
/// Logical identity of the projected document.
pub document_id: DocumentId,
/// The manifest's aggregate G-minor `SchemaVersion`
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pins 8/11): carried verbatim,
/// never derived. This companion has no `epiphany-layout-ir` dependency
/// and cannot decode `ExtensionDeclaration::edit_barriers`, so it cannot
/// recompute this value from the document's other fields — the document
/// author is responsible for updating it when hand-editing barrier bytes
/// that change what tags they name (pin 11's ruled design (a)).
pub manifest_schema_version: SchemaVersion,
/// Optional shared-ancestor identity used for document genealogy.
pub lineage_id: Option<LineageId>,
/// Profile declarations in canonical manifest order.
pub profiles: Vec<ProfileDeclaration>,
/// Extension declarations with every preserved chunk payload inline.
pub extensions: Vec<TextExtension>,
/// Optional canonical base with its snapshot root payload inline.
pub canonical_base: Option<TextCanonicalBase>,
/// Canonically reachable blobs with their payloads inline.
pub blobs: Vec<TextBlob>,
/// Operation envelopes in canonical reduction order.
pub envelopes: Vec<OperationEnvelope>,
}
/// An extension declaration in its text-document form.
///
/// Unlike the bundle's `ExtensionDeclaration`, this type carries preserved
/// chunks as semantic kind/schema/payload triples, not physical `ChunkRef`s.
#[derive(Debug, PartialEq)]
pub struct TextExtension {
/// Opaque identity of the extension.
pub extension_id: ExtensionId,
/// Semantic version of the extension declaration.
pub version: SemVer,
/// Whether an implementation unaware of the extension must refuse editing.
pub required: bool,
/// Preserved extension chunks, inline and ordered by projected form.
pub chunks: Vec<TextChunk>,
/// Canonical opaque encoding of affected object kinds.
pub affected_object_kinds: Vec<u8>,
/// Canonical opaque encoding of the extension's edit barriers.
pub edit_barriers: Vec<u8>,
}
/// One preserved extension chunk with all physical reference data erased.
#[derive(Debug, PartialEq)]
pub struct TextChunk {
/// Semantic role of the chunk.
pub kind: ChunkKind,
/// Schema version governing the payload bytes.
pub schema_version: SchemaVersion,
/// Uncompressed chunk payload carried inline.
pub payload: Vec<u8>,
}
/// A canonical base snapshot in its text-document form.
///
/// The snapshot identity is carried because it is opaque, while the root chunk
/// identity and content hash are derived from its schema and inline payload.
#[derive(Debug, PartialEq)]
pub struct TextCanonicalBase {
/// Opaque snapshot identity, carried verbatim.
pub snapshot_id: SnapshotId,
/// Opaque causal frontier materialized by the snapshot.
pub covers_causal_frontier: FrontierBytes,
/// Reduction algorithm version used to produce the snapshot.
pub reduction_algorithm_version: ReductionAlgorithmVersion,
/// Profile under which the snapshot was produced.
pub profile_id: ProfileId,
/// Schema version of the snapshot root chunk.
pub root_schema_version: SchemaVersion,
/// Uncompressed snapshot root payload carried inline.
pub root_payload: Vec<u8>,
}
/// A canonical blob in its text-document form.
///
/// The payload is inline; its bundle `BlobId`, content hash, offset, lengths,
/// and compression metadata are deliberately absent and are derived or chosen
/// when serialized.
#[derive(Debug, PartialEq)]
pub struct TextBlob {
/// RFC 6838 media type.
pub media_type: String,
/// Optional declared maximum uncompressed size.
pub declared_max_uncompressed_length: Option<u64>,
/// Uncompressed blob payload carried inline.
pub payload: Vec<u8>,
}