UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

137 lines (136 loc) • 10.8 kB
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;