import type { Store } from "../store.js"; import type { GraphSearchHost } from "./types.js"; /** A recognised form: a span of the query that names a node already in the * store. `payload` is that node id. */ export interface Site { start: number; end: number; payload: number; } /** A perceived leaf of the query tree, carrying the node id it resolves to. */ export interface Leaf { start: number; end: number; bytes: Uint8Array; node: number | null; } /** A COMPUTED span: the result of applying a manual rule (an extension's * operation — e.g. the `alu` extension's arithmetic) to a recognised stretch * of the query. Every * bit of the intelligence — recognising the operator (literally or by * resonance), parsing the operands, evaluating the operation — is done by the * caller (src/mind/pipeline.ts), which hands the search the finished result span; the search * treats it exactly like any other recognised completion (a learned fact that * happens to be computed rather than stored). `node` is the canonical id of * the result bytes when the store already holds them, else undefined. */ export interface ComputedResult { i: number; j: number; bytes: Uint8Array; node?: number; } export type GItem = { kind: "cover"; p: number; } | { kind: "form"; i: number; j: number; node: number; via: boolean; /** Set when this form was born by RECOMPOSING rewritten parts into a deeper * learned whole (the fuse of ≥2 recognised completions that names an * edge-bearing node). Its onward continuation is charged at MICRO, not * STEP: once parts are recomposed into a learned form, following that form * to its grounded answer is the recomposition completing — so the * consolidated, more-explanatory reading wins over leaving the parts split. * See {@link GraphSearch.fuse} and {@link GraphSearch.formRules}. */ rcmp?: boolean; /** Set on a concept hop's ASKING form: the edge-less form `node` whose halo * target the caller has not granted yet, held at the hop's own cost and * span. Popping it asks for the target; it leads nowhere itself, so it * is never part of a final derivation (see {@link Licence}). */ ask?: boolean; } | { kind: "out"; i: number; j: number; bytes: Uint8Array; cover: boolean; rec: boolean; node?: number; /** Set only for a {@link ComputedResult} axiom (an extension's derived * value, e.g. ALU arithmetic) — as opposed to a genuinely RECOGNISED * learned form. Both set `rec: true` (a computation bridges the cover * exactly like a learned terminal answer), but they mean different * things for `i..j`'s WIDTH: a recognised form's query-span width is * real evidence of how much of the query's meaning it accounts for * (liftAnswer's half-dominance framing decision); a computed span's * width is operand digit-count, uncorrelated with meaning ("1000 - 421" * is wider than "15 * 7" only because the numbers are bigger, not * because subtraction is more "the point" of its query). See * {@link liftAnswer}. */ computed?: boolean; /** Set on the out emitted at a chain's GENUINE FIXPOINT — the one span kind a * recursive re-cover ({@link GraphSearch.recompleteNode}) may deepen. The * re-cover does NOT run here: this marks the span as eligible, and * {@link GraphSearch.deepen} runs it afterwards on the spans the lightest * derivation actually CHOSE. Part of {@link key}, because it decides * whether the span's final bytes may still change. */ fix?: boolean; /** Set on the out DERIVE-THROUGH produced: a produced fact's own contained * entity (the subject the query never named) combined with the query's tail * named a learned key, and that key's continuation is this out. Part of * {@link key} so the derived reading is a distinct chart item from the plain * concatenation of the same bytes. Named for what it does — derive THROUGH * a produced fact — because `join` is already the confluence mechanism's * `Provenance`, a different act at a different layer. */ throughFact?: boolean; }; export declare const STEP = 1; export declare const CONCEPT = 10; export declare const PASS = 1000; /** The cheapest local cost in the ladder: a recognised completion bridging into * the cover. Far below STEP, so connecting two recognised spans never disturbs * the ordering — and, being the minimum per-position cost, it is the per-byte * unit of the admissible search heuristic (see {@link GraphSearch.buildSearch}). */ export declare const MICRO = 0.001; /** One chosen span of the cover, left to right. `node` is the graph node the * span resolved to (when known) — for a recognised completion it is the chain's * terminal node, the foothold the bridge walks edges from. */ export interface Seg { i: number; j: number; bytes: Uint8Array; rec: boolean; node?: number; /** See the `computed` field of the "out" {@link GItem} — set only for an * extension's derived value, never a genuinely recognised learned form. */ computed?: boolean; /** See the `fix` field of the "out" {@link GItem} — this span ended a chain at * a genuine fixpoint, so it is the one span kind a recursive re-cover may * still deepen. Consumed by {@link GraphSearch.deepen}. */ fix?: boolean; } /** One rule application inside the cover's lightest derivation — the FINEST * grain of Sema's core reasoning, one node of the adapted A*LD proof tree. `move` * names which deduction rule fired (the reasoning act); `premises` and * `conclusion` are the items it consumed and produced, each rendered as a * positioned, possibly-node-bearing span; `cost` is the rule's local weight * (STEP for a learned edge, CONCEPT for a synonym hop, 0 for a free fuse, …); * `order` is its post-order position so a tracer can replay the proof in the * order it was built (premises before conclusion). */ export interface DerivationStep { order: number; move: DerivationMove; premises: DerivationItem[]; conclusion: DerivationItem; cost: number; /** The `order`s of the steps that produced this one's premises — the EXACT * data-flow edges of the proof tree (a `bridge` names the `ground` whose * `out` it crossed and the earlier `cover` it extended). A premise that is * an axiom (a seed leaf/form/computed result, never a rule conclusion) has no * producer and contributes no edge, so this lists only the derived premises — * the finest dependency structure the inference holds. */ producers: number[]; } /** A premise or conclusion of a {@link DerivationStep}, flattened from a {@link * GItem} into the fields a rationale cares about. */ export interface DerivationItem { kind: "cover" | "form" | "out"; /** `[i, j)` span for a form/out; for a cover item, `[p, p]`. */ span: [number, number]; /** The bytes an `out` carries (the only item kind that holds output bytes). */ bytes?: Uint8Array; /** The graph node a form/out resolves to, when known. */ node?: number; } /** The reasoning act a derivation rule performs — the human name for which of * {@link GraphSearch}'s rules fired, recovered from the rule's premise/ * conclusion shape (the rules carry no label, so this classifies by structure, * the single place that maps rule geometry to a name). */ export type DerivationMove = "axiom" | "follow-edge" | "concept-hop" | "voice" | "ground" | "splice-connector" | "split" | "fuse" | "recompose" | "derive-through" | "bridge" | "pool-vote" | "step"; /** THE ASYNC PREMISES A COVER USES, RESOLVED ONLY WHERE THE SEARCH REACHES THEM. * * Two of the cover's rules need data the synchronous search cannot fetch — a * connector splice (a bridge between two answers) and a concept hop (an * edge-less form's halo sibling) — and both used to be resolved for EVERY * candidate before the search ran. A rule fires only from popped premises, * and the agenda pops in cost order up to the goal, so most of that work fed * rules the search never reached: measured on the 31.7M-node store, a 262-byte * query recognised whole paid 23 bridges (143,520 junction pops, 7.3 s of its * 7.9 s) for sub-forms whose rewrites cost a STEP each and so never left the * agenda before the one-STEP goal — and then 1.2 s of halo reads for concept * hops priced at CONCEPT, ten times that goal. * * So the caller OFFERS what it can resolve; where the search reaches an offered * key it uses a GRANTED value and ASKS for an ungranted one — a connector when * its splice's premises meet, a concept target when the hop's conclusion, held * as an asking form at the hop's own cost, is popped. A run that asked * nothing is the run every key resolved in advance would have made: the pops * are decided only by the rules fired from popped items, a granted key fires * the very rule it fired before, and an asking form that was never popped fired * none. So its cover is final. A run that asked is PROVISIONAL: the caller * grants the asked keys (the async reads the search cannot run) and covers * again; every round grants at least one offered key, so the rounds end. */ export interface Licence { /** Every key the caller can resolve. */ readonly offered: ReadonlySet; /** The keys resolved so far: their value, or null (nothing learnt). */ readonly granted: ReadonlyMap; /** Filled by the search: the offered keys it reached ungranted. */ readonly asked: Set; } /** Connectors, keyed `L,R` by answer-node ids, valued by the learnt glue. */ export type ConnectorLicence = Licence; /** Concept hops, keyed by an edge-less form's node, valued by the node its * halo sibling's edge leads to. */ export type ConceptLicence = Licence; /** Nothing offered: a cover with no async premises (a nested completion, * articulation's revoicing). */ export declare function noLicence(): Licence; /** The lightest-derivation search over the Sema graph. One instance binds the * store, `maxGroup` (the fusible span ceiling), and the canonical * {@link resolve} callback; {@link cover} then solves one query. */ export declare class GraphSearch { private readonly store; private readonly maxGroup; /** The host whose capabilities the search consults: resolve (canonical node * id of a byte span), recogniseSpan (content-addressed graph lookup for * recursive completion), and chooseNext (distributional-evidence edge * disambiguation when a recognised form has multiple continuations). */ private readonly host; constructor(store: Store, maxGroup: number, /** The host whose capabilities the search consults: resolve (canonical node * id of a byte span), recogniseSpan (content-addressed graph lookup for * recursive completion), and chooseNext (distributional-evidence edge * disambiguation when a recognised form has multiple continuations). */ host: GraphSearchHost); /** The hub bound √N (bounded-reads.md) — the ONE fan-out cap, read over the * store this module holds (it never holds a MindContext). */ private hubBound; /** Explore the Sema graph for the lightest cover of the query and return its * chosen spans left-to-right — WITH the derivation's total weight (the g * value of the goal item, in the exported cost ladder), which think's * grounding decider compares against the other mechanisms' candidates — * or null if the query cannot be covered. * * The search runs on the query's tree leaves, not flat bytes — leaf-level * cover axioms, recognised forms as graph entry points — and discovers * cross-leaf forms by fusing adjacent fragments on demand (the only * byte-processing it does, and only where a derivation needs it). When * `substitutions` is given, recognised forms emit substitute bytes directly * (cost 0) — articulation splicing the asker's wording in. * * Any learnt connector between two rewrites is spliced IN by the in-search * connector rule (see {@link outRules}), so the returned spans already carry * it — there is no post-pass. With a {@link ConnectorLicence}, a cover * returned while `connectors.asked` is non-empty is PROVISIONAL: grant the * asked pairs and cover again. */ cover(queryLen: number, sites: ReadonlyArray, concepts: ConceptLicence, leaves: ReadonlyArray, splits: ReadonlySet, substitutions?: ReadonlyMap, connectors?: ConnectorLicence, computedResults?: ReadonlyArray, /** When given, receives each solved span's lightest derivation — the full * adapted A*LD proof tree as classified {@link DerivationStep}s — for the * TOP cover AND every nested completion the sink is threaded into (see * {@link recompleteNode}), so a produced form's own recompositions reach * the rationale instead of stopping at the first layer. Off by default, so * the search pays nothing when no one inspects. */ onDerivation?: (steps: DerivationStep[]) => void): { segs: Seg[]; cost: number; moves: number; } | null; /** Build the deduction system for one span and return its lightest cover's * chosen spans — the SINGLE routine the query and every produced composite * run through. `recognition` carries the span's recognised forms; the query * brings its own (with pre-resolved concepts/connectors), a recursive * completion re-recognises the produced bytes (edge/fuse only). * * No depth limit governs nesting — convergence is INTRINSIC, exactly as in the * adapted A*LD chart and {@link completeForward}: a completion only recurses into a * node it has not already entered ({@link recompleteNode}'s cycle guard), and * the node ids are finite, so the recursion must terminate on its own. A * decomposition may therefore run as deep as the graph licenses — three * decomposes, two recomposes, any mix — and stops only when it reaches a node * that leads nowhere new, never at an arbitrary count. */ private solve; /** Re-cover the CHOSEN fixpoint spans, in place. * * Completion is still "cover, recursively" — it just runs on the answer * instead of on the exploration. A cover chooses O(segs) spans, so a level * pays O(answer) re-covers however densely the corpus interconnects the forms * the search passed through on the way. That is the bound * {@link recompleteNode}'s contract always claimed and, running per fixpoint * REACHED, never had. * * Deepening cannot change which cover won: the derivation is already final * and every span keeps its i..j and its cost. It only replaces a chosen * span's bytes with the deeper learnt form they rewrite to — what the * recursion was always for. */ private deepen; /** The weighted deduction system the graph exploration solves (the four * reductions of adapted A*LD live in {@link lightestDerivation}; this only states the * items, axioms, goal, and rules — see {@link GItem} for the item kinds). * * Forms that span across leaves are discovered BY the rules, which fuse * adjacent fragments toward a known leaf (findLeaf) or branch (findBranch); * a completion fused with its neighbour may spell a deeper learned form the * flat probes can't name, recovered canonically by {@link resolve}. */ private buildSearch; /** cover(p): the BRIDGE rule — extend the cover across any coverable out that * begins at p, stepping the frontier from p to that out's end. * * This is what links the rewritten parts of a multi-form answer. An out is * either a recognised completion (`rec`, free) or a literal span the query * carried between known forms — the connective: a space, comma, period, * newline, or any run of bytes that was never recognised. Bridging a literal * costs PASS per byte (so the search still prefers to recognise), but it is * the cheapest — indeed only — way to cross a gap that has no learned form, * and it KEEPS that connective in the cover chain, so the asker's own linking * material reappears when it still coheres ("ice, fire" → "cold, hot", not * "coldhot"). A recognised completion bridges for a tiny ε (1e-3, far below * STEP), so a single connected span beats two separate ones on coherence * without disturbing the cost ladder's ordering. * * Marks p finalised so the symmetric out-side bridge ({@link outRules}) can * fire for outs that arrive after their start position is covered. */ private coverRules; /** The BRIDGE rule, built once for its two arrival orders (cover-first in * {@link coverRules}, out-first in {@link outRules}): ε for a recognised * completion; PASS per byte for a literal connective — kept in the cover * so the asker's own connector survives where it fits. ONE definition of * the cost expression, so the ladder's application cannot drift between * the two sides. */ private bridgeRule; /** The connector-SPLICE rule for an oriented (l, r) pair, or null when the * pair does not qualify — the ONE body behind {@link outRules}' two * mirror loops (this-as-left over offered right partners, this-as-right * over offered left partners). Fires only when both sides are * recognised, r starts at or after l ends, and the gap between them is * empty or wholly recognised — never across the asker's own literal * separator — and the pair's connector is granted and learnt. A qualifying * pair not yet granted is ASKED for (see {@link ConnectorLicence}). */ private trySplice; /** form(i,j,node,via): follow the graph out of `node`, or (in articulation) * emit its substitute voice directly. */ private formRules; /** Complete a node that an edge produced but that bears no further whole-edge, * by COVERING ITS OWN BYTES — the very same operation the top query runs. * Completion is not a special pass: a produced composite ("p1 p2") is just * another span to cover, and covering it re-applies recognition, edge-follow, * fusion and recomposition to discover that its parts continue (p1→R1, p2→R2) * and recompose into a deeper learnt form (→ FINAL). This is why a single * edge-target needs no bespoke logic — it routes back through {@link solve}. * * The produced bytes are decomposed by the machinery that owns their shape: * the span's LEAVES and SPLITS drive the split rule, which resolves each half * through findLeaf — so a part that straddles a content-defined cut is still * recovered even though the node's tree children need not align with the * learnt parts (the fold cuts "p1 p2" as "p1 p"|"2"). Recognised SITES are * filtered to the node's own kids: a produced form is completed out of what * it was built from, never by re-recognising arbitrary forms inside it. That * filter is load-bearing — a 37-byte dialogue sentence carries seven hub * openers, and re-covering them chained through the corpus's whole * continuation population: 2.5 GB and OOM for `respond("hi.")`. * * The recovered answer is accepted only when it MOVED and names a LEARNT node * ({@link resolve}) — the graph itself gates against re-expanding a contained * form ("ice is cold" ⊅→ "ice is cold is cold"). An ACCEPTED completion is * then re-covered in turn, so a chain runs as deep as the graph licenses; a * rejected one ends its branch, so no work is spent past it. * * Termination and cost are STRUCTURAL: {@link recompleteOpen} is the chain's * stack (membership is the cycle guard), {@link recompleteMemo} re-covers each * node at most once, only accepted completions recurse, and every level * deepens only the top derivation — so a cover pays for the chain it finds, * not for how densely the corpus interconnects the forms it passes through. * Guard: test/89-completion-recursion.test.mjs. */ private recompleteNode; /** Per-cover memo of each produced node's completion (so the many terminal * outs of a long query re-cover each distinct node at most once); reset at the * top of {@link cover}. */ private recompleteMemo; /** {@link entityProposals}, per fact BYTES — a pure function of them while * the store is read-only (one response), so a fact the chart reaches as * several items (cover/fix variants, nested completions) is scanned once. * Reset at the top of {@link cover}, like {@link recompleteMemo}. */ private entityMemo; /** The joins licensed under one cover's {@link Licence}, kept across its * re-covers (see {@link solve}). */ private readonly joinsKept; /** The derivation sink of the TOP cover, threaded into every nested * completion so a produced form's own recompositions are reported in the * same trace instead of vanishing after the first layer. Undefined when * nothing is inspecting, so an uninspected response pays nothing. */ private derivationSink?; /** The chain of nodes currently being re-completed — the recursion STACK. * MEMBERSHIP is the cycle guard ({@link recompleteNode} refuses a node * already open on this chain), which is what lets a completion recurse as * deep as the graph licenses while the work stays the answer's: the * recursion only advances through an ACCEPTED completion, every produced * form is decomposed into its own kids, and the memo re-covers each node at * most once per cover. A Set, not a flag, because it states WHICH node is * open — the invariant a reader needs to check the guard. */ private recompleteOpen; /** The ENTITIES a produced fact's bytes contain that lead somewhere — the * candidates a join may travel through. The forms the fact CONTAINS, by the * same recogniser the query went through (so the evidence standard is the * query's), plus — because the recognition of a STORED WHOLE returns the * whole and stops (measured: one site, the fact's own node, for `The * director of Eva is Gustaf Molander.`) — the longest canonically-resolving * form at each offset. A byte atom is never a subject. The scan runs only * for a FORM (≥ W: per letter it measured 20-26 s in test/99), and each probe * is a `canonResolve` whose exact tier decides a miss by its segment probe, * without a fold. The admission predicate is the store's `leadsSomewhere` * (edge or halo); the host lends its memoised form when it can. * * The SOURCE of each proposal travels with it, so a refusal names which path * proposed the candidate. Memoised per fact bytes ({@link entityMemo}). */ private entityProposals; /** DERIVE-THROUGH — a RULE this module's DeductionSystem was missing. The * A*LD library is untouched: this is one more `premises → conclusion + cost` * rule in the system {@link buildSearch} hands to {@link lightestDerivation}, * the same kind of rule as `fuse`/`recompose` — not an extension of * `src/derive`, and not a grounding mechanism. It derives the answer THROUGH * a produced fact, without the intermediate key being named in the query. * * A produced fact (`fact.node`) carries the subject the query reached but * never wrote; the query's remaining tail names the relation to follow from * it. The pair IS a learned key — `""` — so the rule asks the * store for that key's continuation and, when it exists, concludes with the * fact reached through it. On the ladder it is one STEP: a direct edge, * exactly as following a literal continuation is. Deterministic and * point-probed (`resolve` + `nextFirst`, no scan), so it adds no read that * grows with the corpus. Asked ONCE per fact a lightest derivation stood on * (the join license, {@link solve}) — never per fact the exploration merely * reached — so the number of facts it prices is the answer's, not the * corpus's. The move is visible in the rationale as its own act * (`classifyMove` reports `derive-through`), distinct from the * byte-concatenating `fuse`/`splice` — and named `derive-through` rather than * `join` so it cannot be read as the confluence mechanism's `Provenance`. */ private deriveThrough; /** out(i,j,bytes,…): index it for the binary rules, then offer splicing a * learnt connector (the in-search bridge), splitting (at a sub-leaf form * boundary), bridging (cover(i) ∧ this → cover(j)), fusing with an adjacent * finalised out, and — for a LICENSED fact — JOINING the entity it contains * with the query's tail ({@link deriveThrough}, granted in {@link solve}). */ private outRules; /** Whether the query span [from, to) is wholly covered by RECOGNISED outs — * the test that lets a connector jump across INTERIOR answers (an N-ary whole) * but never across the asker's own unrecognised framing (a space or comma the * asker wrote between parts). Empty span (from === to, the adjacent case) is * trivially recognised. Otherwise step right-to-left: from `to`, find a * recognised out ending there and continue from its start, until reaching * `from`. Greedy-longest is sufficient here — the spans in play are the few * recognised answers of one query, not a general interval cover. */ private gapRecognised; /** Fuse two adjacent finalised outs — the search's own discovery of forms * that cross leaf boundaries. The concatenation may be a known leaf * (findLeaf, when short enough), or — when both sides resolved — their pair a * known branch (findBranch); a completion fused with its neighbour may spell * a deeper learned form, recovered canonically by {@link resolve} (gated on a * completion being present, so it only runs along chains). The fused span * lives on as an intermediate out while it could still grow into a form, and * enters the graph as a form the moment it names a node. */ private fuse; }