@sema-agent/core
Version:
Stateless, task-oriented AI agent core
137 lines (136 loc) • 10.8 kB
TypeScript
import { type MemoryEntry, type MemoryEntryDistilled, type MemoryEntryFrontmatter, type MemoryEntryOrigin } from "./types.js";
/** A parsed entry file: frontmatter (may be absent — CC allows bare files; harvest mints them) + body. */
export interface ParsedEntryFile {
/** The hidden immutable id (`id:` frontmatter line), when present. */
id?: string;
frontmatter: MemoryEntryFrontmatter;
body: string;
/** True when the file had a well-formed `---` frontmatter block at all. */
hadFrontmatter: boolean;
}
/** The id-shape contract, shared with the WRITE path (KNOWN-ISSUES 幽灵行加固): the File backend's
* applyPatches refuses any patch whose id this rejects — such an entry would be applied + ledgered,
* yet THIS parser drops the id on read (a fresh one gets minted), leaving the file on disk but the
* id permanently invisible. Read gate and write gate must stay one predicate. */
export declare function isValidEntryId(value: string): boolean;
/** Parse one memory file's text into frontmatter + body. Never throws — an unfenced file is all-body. */
export declare function parseEntryFile(text: string): ParsedEntryFile;
/** Serialize an entry to its canonical file projection (id FIRST — the hidden immutable field). */
export declare function serializeEntryFile(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
/**
* Content-hash rev (the CAS baseline). Computed over the SEMANTIC content — id, structured
* frontmatter fields, preserved extra lines, and body — via a canonical JSON tuple, NOT raw file
* bytes: engine (parsing disk files) and backend (storing entries) must agree on the rev regardless
* of incidental serialization formatting, or every materialize would see a phantom diff.
*/
export declare function computeEntryRev(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
/** True ⇔ `extra` carries origin-form bytes (an invalid/foreign origin block preserved verbatim,
* or an origin block a pre-336 vintage folded into `extra`). */
export declare function hasOriginFormExtra(fm: Pick<MemoryEntryFrontmatter, "extra">): boolean;
/**
* design/336 slice 2 — the ONE serialization-closed tokenization of `extra`'s origin-form bytes
* (the adversarial round-3 deep fix; every predicate below reads THROUGH this walk, never through
* a private re-implementation). A CARRIER is one independent origin representation:
* - a bare block header (`origin:` — judged on the TRIMMED line, so indented spellings count),
* together with its indented continuation lines: ONE carrier, normalized field-by-field
* (first-wins per key; unknown sub-lines tolerated — an in-block `origin: …` scalar is part of
* THIS carrier, never its own);
* - a scalar `origin: …` line OUTSIDE any block: ONE carrier, always the sentinel (a scalar names
* no fields).
* Per-carrier normalization is INDEPENDENT on purpose (adversarial round 2: a cross-carrier
* accumulation let two individually-invalid blocks combine into a valid marker, and parse-time
* block reordering then changed which fields won): a carrier whose own fields form a valid origin
* (taint external, `at` present AND serialization-stable, cause absent-or-known) normalizes to
* that value; anything else normalizes to the `{ taint: "external", at: 0 }` sentinel (cause
* absent, `at` = 0 — a clock here would make engine and backend normalize to different values and
* the engine's own patches would never converge). Marker-form bytes are NEVER dropped and NEVER
* read as clean absence.
*/
export declare function extraOriginCarriers(extra: readonly string[] | undefined): MemoryEntryOrigin[];
/**
* design/336 §2.2 — the ONE reading of an entry's committed external-origin marker: the typed
* field when present, else the first `extra` carrier's deterministic normalization
* ({@link extraOriginCarriers}). Returns `undefined` ⇔ the entry carries no marker in any form.
* The first-carrier choice is immaterial on every committable shape: a representation whose
* carriers DISAGREE is refused at every inbound boundary ({@link ambiguousOriginRepresentation}),
* so a committed multi-carrier shape always answers the one agreed value.
*/
export declare function committedOriginOf(fm: Pick<MemoryEntryFrontmatter, "origin" | "extra">): MemoryEntryOrigin | undefined;
/**
* design/383 §1.6 — the CONSOLIDATION exposure predicate: is this entry's content untrusted for
* the purpose of the distillation fold? TWO axes, not one:
* - a committed external-origin marker ({@link committedOriginOf}) — the design/336 axis;
* - `trust: "untrusted"` — the design-A C-5 repo-ingest marker, which an ingested repo file can
* carry with NO origin marker at all. Such an entry is untrusted content by the same law that
* makes it un-whitewashable (the single-value marker has no `"trusted"` spelling to escalate
* to), and the origin-only predicate sorted it into the CLEAN arm — its bytes then authored
* clean products, and those products committed unmarked. That is exactly the marked-ingress
* laundering window the exposure partition exists to close.
*
* The direction is deliberately conservative (over-marking): an entry judged marked here is
* withheld from the clean arm and its products carry `external/derived`. Nothing here proves an
* unmarked entry clean — absence of both markers means "never judged exposed", as always.
*
* This is the consolidation lane's judgment only. The READ-face treatment (opaque handles, band
* ordering, the `MemoryEntryHeader.exposure` backend fact) stays on the origin axis alone: those
* faces answer "does the store carry an origin marker", a data question a third-party backend
* already answers, and widening them is a backend-contract change this predicate does not make.
*/
export declare function consolidationExposedFrontmatter(fm: Pick<MemoryEntryFrontmatter, "origin" | "extra" | "trust">): boolean;
/** Deep equality over the origin marker (every member, absence included) — the immutability law's
* comparator: a downgraded cause or a rewritten `at` is as refused as a strip. */
export declare function originEquals(a: MemoryEntryOrigin | undefined, b: MemoryEntryOrigin | undefined): boolean;
/**
* design/336 (adversarial rounds 1-3) — the AMBIGUOUS origin-representation predicate, ONE law for
* every inbound boundary (backend patch law, sync wire, bundle validation, out-of-band adoption):
* a representation is refused ⇔ its carriers DISAGREE — any two of {the typed field, each `extra`
* carrier's independent normalization ({@link extraOriginCarriers})} differ on any member — or the
* typed `at` is not serialization-stable (a value that cannot survive its own serialization is a
* representation conflict with the entry's next parse). Covered vehicles: typed A + disagreeing
* extra block B (round 1 — the typed member satisfies the immutability comparator while B rides
* into storage and a reparse answers B); disagreeing extra-only blocks (round 2 — cross-block
* field combination plus parse-time block reordering rewrite members with no typed origin at all).
*
* The refusal boundary keys on DISAGREEMENT, not on carrier count (slice-2 serialization closure,
* the round-3 deep fix): agreeing carriers are never a member-rewrite vehicle — refusing them by
* count alone made the verdict flip across a serialize/parse cycle (a promotion reseats one
* carrier without changing any value). This generalizes the old "typed beside EXACTLY ONE equal
* extra form" legacy-carriage exception: N agreeing representations are that same carriage, so
* carry-forward of old committed forms never bricks. The model-writable face never reaches this
* predicate — the harvest strips every origin form and re-judges (§2.3-1). Together with the
* pristine-only typed seating (parse) and the carrier-canonical rev ({@link computeEntryRev}),
* the accepted set is SERIALIZATION-CLOSED: carrier count, normalized origin, ambiguity verdict
* and rev are all invariant across serialize/parse at every boundary (property-tested).
*/
export declare function ambiguousOriginRepresentation(fm: Pick<MemoryEntryFrontmatter, "origin" | "extra">): boolean;
/**
* design/336 §2.3-1 — the harvest-side STRIP of model-written origin bytes (typed field AND
* origin-form `extra` lines, a block header's indented continuation included). The origin seat is
* engine-authored in every mode: whatever a file on the model-writable plane says about its own
* origin is not evidence — the engine strips it and mints (or carries forward) its own judgment.
* Same family as the invalid-id "dropped, fresh one minted" exception to the round-trip law.
* MUTATES the given (already-copied) frontmatter; returns true ⇔ anything was stripped (the
* caller discloses via a report warning).
*/
export declare function stripModelWrittenOrigin(fm: MemoryEntryFrontmatter): boolean;
/** True ⇔ `extra` carries distilled-form bytes (a non-pristine/foreign block preserved verbatim). */
export declare function hasDistilledFormExtra(fm: Pick<MemoryEntryFrontmatter, "extra">): boolean;
/** The ONE reading of an entry's committed distilled block: the typed seat or nothing. Extra-form
* bytes deliberately do NOT answer here (they mint no edge — fewer edges is the safe direction
* for a suppression carrier). Returns a defensive copy. */
export declare function committedDistilledOf(fm: Pick<MemoryEntryFrontmatter, "distilled">): MemoryEntryDistilled | undefined;
/** Deep equality over the distilled block (every member, input order included) — the immutability
* law's comparator: a rewritten carrierRev, a dropped input row or a flipped superseded flag is as
* refused as a strip. */
export declare function distilledEquals(a: MemoryEntryDistilled | undefined, b: MemoryEntryDistilled | undefined): boolean;
/**
* design/339 §1.4.2-1 — the harvest-side STRIP of model-written distilled bytes (typed field AND
* distilled-form `extra` lines): the block is minted by the consolidation plan freeze EXCLUSIVELY —
* whatever a file on the model-writable plane says about its own lineage/supersession is not
* evidence (a model-writable edge would be the covert-delete channel add-only exists to close).
* Same seat and same disclosure discipline as {@link stripModelWrittenOrigin}. MUTATES the given
* (already-copied) frontmatter; returns true ⇔ anything was stripped.
*/
export declare function stripModelWrittenDistilled(fm: MemoryEntryFrontmatter): boolean;
/** Parse a file's text directly into a full MemoryEntry (id must already be present/decided). */
export declare function entryFromFile(text: string, id: string, slug: string, scope: string): MemoryEntry;