@hviana/sema 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/example/demo.d.ts +1 -0
- package/dist/example/demo.js +39 -0
- package/dist/example/train_base.d.ts +87 -0
- package/dist/example/train_base.js +2252 -0
- package/dist/src/alphabet.d.ts +7 -0
- package/dist/src/alphabet.js +33 -0
- package/dist/src/alu/src/alu.d.ts +185 -0
- package/dist/src/alu/src/alu.js +440 -0
- package/dist/src/alu/src/expr.d.ts +61 -0
- package/dist/src/alu/src/expr.js +318 -0
- package/dist/src/alu/src/index.d.ts +11 -0
- package/dist/src/alu/src/index.js +19 -0
- package/dist/src/alu/src/kernel-arith.d.ts +16 -0
- package/dist/src/alu/src/kernel-arith.js +264 -0
- package/dist/src/alu/src/kernel-bits.d.ts +19 -0
- package/dist/src/alu/src/kernel-bits.js +152 -0
- package/dist/src/alu/src/kernel-logic.d.ts +4 -0
- package/dist/src/alu/src/kernel-logic.js +60 -0
- package/dist/src/alu/src/kernel-nd.d.ts +3 -0
- package/dist/src/alu/src/kernel-nd.js +208 -0
- package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
- package/dist/src/alu/src/kernel-numeric.js +366 -0
- package/dist/src/alu/src/operation.d.ts +168 -0
- package/dist/src/alu/src/operation.js +189 -0
- package/dist/src/alu/src/parser.d.ts +221 -0
- package/dist/src/alu/src/parser.js +577 -0
- package/dist/src/alu/src/resonance.d.ts +55 -0
- package/dist/src/alu/src/resonance.js +126 -0
- package/dist/src/alu/src/text.d.ts +31 -0
- package/dist/src/alu/src/text.js +73 -0
- package/dist/src/alu/src/value.d.ts +109 -0
- package/dist/src/alu/src/value.js +300 -0
- package/dist/src/alu/test/alu.test.d.ts +1 -0
- package/dist/src/alu/test/alu.test.js +764 -0
- package/dist/src/bytes.d.ts +14 -0
- package/dist/src/bytes.js +59 -0
- package/dist/src/canon.d.ts +26 -0
- package/dist/src/canon.js +57 -0
- package/dist/src/config.d.ts +111 -0
- package/dist/src/config.js +91 -0
- package/dist/src/derive/src/deduction.d.ts +125 -0
- package/dist/src/derive/src/deduction.js +155 -0
- package/dist/src/derive/src/index.d.ts +7 -0
- package/dist/src/derive/src/index.js +11 -0
- package/dist/src/derive/src/priority-queue.d.ts +20 -0
- package/dist/src/derive/src/priority-queue.js +73 -0
- package/dist/src/derive/src/rewrite.d.ts +56 -0
- package/dist/src/derive/src/rewrite.js +100 -0
- package/dist/src/derive/src/trie.d.ts +90 -0
- package/dist/src/derive/src/trie.js +217 -0
- package/dist/src/derive/test/derive.test.d.ts +1 -0
- package/dist/src/derive/test/derive.test.js +122 -0
- package/dist/src/extension.d.ts +37 -0
- package/dist/src/extension.js +7 -0
- package/dist/src/geometry.d.ts +148 -0
- package/dist/src/geometry.js +511 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +18 -0
- package/dist/src/ingest-cache.d.ts +41 -0
- package/dist/src/ingest-cache.js +161 -0
- package/dist/src/mind/articulation.d.ts +6 -0
- package/dist/src/mind/articulation.js +99 -0
- package/dist/src/mind/attention.d.ts +72 -0
- package/dist/src/mind/attention.js +898 -0
- package/dist/src/mind/canonical.d.ts +29 -0
- package/dist/src/mind/canonical.js +88 -0
- package/dist/src/mind/graph-search.d.ts +270 -0
- package/dist/src/mind/graph-search.js +927 -0
- package/dist/src/mind/index.d.ts +5 -0
- package/dist/src/mind/index.js +5 -0
- package/dist/src/mind/junction.d.ts +95 -0
- package/dist/src/mind/junction.js +262 -0
- package/dist/src/mind/learning.d.ts +47 -0
- package/dist/src/mind/learning.js +233 -0
- package/dist/src/mind/match.d.ts +113 -0
- package/dist/src/mind/match.js +446 -0
- package/dist/src/mind/mechanisms/alu.d.ts +4 -0
- package/dist/src/mind/mechanisms/alu.js +29 -0
- package/dist/src/mind/mechanisms/cast.d.ts +35 -0
- package/dist/src/mind/mechanisms/cast.js +461 -0
- package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
- package/dist/src/mind/mechanisms/confluence.js +225 -0
- package/dist/src/mind/mechanisms/cover.d.ts +6 -0
- package/dist/src/mind/mechanisms/cover.js +179 -0
- package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
- package/dist/src/mind/mechanisms/extraction.js +342 -0
- package/dist/src/mind/mechanisms/recall.d.ts +13 -0
- package/dist/src/mind/mechanisms/recall.js +235 -0
- package/dist/src/mind/mind.d.ts +273 -0
- package/dist/src/mind/mind.js +598 -0
- package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
- package/dist/src/mind/pipeline-mechanism.js +213 -0
- package/dist/src/mind/pipeline.d.ts +20 -0
- package/dist/src/mind/pipeline.js +185 -0
- package/dist/src/mind/primitives.d.ts +63 -0
- package/dist/src/mind/primitives.js +251 -0
- package/dist/src/mind/rationale.d.ts +134 -0
- package/dist/src/mind/rationale.js +162 -0
- package/dist/src/mind/reasoning.d.ts +15 -0
- package/dist/src/mind/reasoning.js +162 -0
- package/dist/src/mind/recognition.d.ts +20 -0
- package/dist/src/mind/recognition.js +246 -0
- package/dist/src/mind/resonance.d.ts +23 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +15 -0
- package/dist/src/mind/trace.js +73 -0
- package/dist/src/mind/traverse.d.ts +113 -0
- package/dist/src/mind/traverse.js +488 -0
- package/dist/src/mind/types.d.ts +186 -0
- package/dist/src/mind/types.js +84 -0
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/rabitq-ivf/src/index.js +4 -0
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +31 -0
- package/dist/src/sema.js +63 -0
- package/dist/src/store-sqlite.d.ts +184 -0
- package/dist/src/store-sqlite.js +942 -0
- package/dist/src/store.d.ts +654 -0
- package/dist/src/store.js +1612 -0
- package/dist/src/vec.d.ts +31 -0
- package/dist/src/vec.js +109 -0
- package/package.json +1 -1
- package/src/mind/graph-search.ts +128 -46
- package/src/mind/mind.ts +37 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Mind } from "./mind.js";
|
|
2
|
+
export type { Input, Response } from "./mind.js";
|
|
3
|
+
export type { ComputedSpan, ExtensionHost } from "./mind.js";
|
|
4
|
+
export type { MechanismResult, PipelineMechanism, Precomputed, } from "./pipeline-mechanism.js";
|
|
5
|
+
export type { InspectRationale, RationaleItem, RationaleStep, } from "./rationale.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { MindContext } from "./types.js";
|
|
2
|
+
export interface Junction {
|
|
3
|
+
/** The node whose learnt bytes evidence this junction (a container form,
|
|
4
|
+
* a continuation, or a context). */
|
|
5
|
+
id: number;
|
|
6
|
+
/** The bytes that belong between left and right. */
|
|
7
|
+
interior: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
10
|
+
* node when it is a stored form, plus — when the node has no structural
|
|
11
|
+
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
12
|
+
* parents, but its constituent W-grams typically do; the window ids
|
|
13
|
+
* provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
|
|
14
|
+
* single indexed lookup, far cheaper than computing every window id, so
|
|
15
|
+
* the window-id path is only taken when the node alone cannot ascend.
|
|
16
|
+
* Exported for callers (synonym junctions) that hold one side FIXED across
|
|
17
|
+
* several calls and so compute its seeds once instead of per call. */
|
|
18
|
+
export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[];
|
|
19
|
+
/** Per-response cache of the identity walks' pure reads (capped bytes,
|
|
20
|
+
* parent pages, container pages), keyed by the response lifecycle object
|
|
21
|
+
* (ctx.climbMemo). One response issues many walks whose ancestries overlap
|
|
22
|
+
* heavily (pair sides repeat across combos, and synonym walks revisit the
|
|
23
|
+
* same neighbourhoods); the store is read-only while a response is in flight,
|
|
24
|
+
* so every one of these reads is a pure function of the id — repeats cost a
|
|
25
|
+
* Map hit instead of a SQL statement or a byte reconstruction. */
|
|
26
|
+
export interface WalkCache {
|
|
27
|
+
/** id → prefix bytes read so far + whether they are the COMPLETE bytes
|
|
28
|
+
* (shorter than the cap that read them). */
|
|
29
|
+
reads: Map<number, {
|
|
30
|
+
b: Uint8Array;
|
|
31
|
+
complete: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
parents: Map<number, number[]>;
|
|
34
|
+
containers: Map<number, number[]>;
|
|
35
|
+
}
|
|
36
|
+
export declare function walkCache(ctx: MindContext): WalkCache | null;
|
|
37
|
+
export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
|
|
38
|
+
/** Tier 1 body, parameterised on already-resolved seed lists so a caller
|
|
39
|
+
* holding one side FIXED across several calls (synonym junctions) pays for
|
|
40
|
+
* that side's seeds once, not once per call. The byte-containment check
|
|
41
|
+
* below ensures only genuine containers are returned regardless of seeds.
|
|
42
|
+
*
|
|
43
|
+
* BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
|
|
44
|
+
* store, where the unbounded form spent >90% of a query's CPU here):
|
|
45
|
+
* • PHRASE-SCALE READS — a junction container is by contract a whole the
|
|
46
|
+
* pair nearly exhausts (glue from a period to a phrase), so every visit
|
|
47
|
+
* reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
|
|
48
|
+
* A node whose bytes exceed the cap cannot be a junction container, and
|
|
49
|
+
* its ancestors are strictly larger — the branch is PRUNED, never
|
|
50
|
+
* reconstructing a corpus-sized deposit (an oasst2 conversation) just
|
|
51
|
+
* to reject it.
|
|
52
|
+
* • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
|
|
53
|
+
* frontier (the one fan-out convention) through the ~W structural
|
|
54
|
+
* levels that separate phrase-scale content from its containers
|
|
55
|
+
* (perception trees are W-ary, so a junction container lies within a
|
|
56
|
+
* few levels of its parts). A side too common to decide within the
|
|
57
|
+
* budget abstains here and falls through to the resonance tier (the
|
|
58
|
+
* climb's own saturation semantics).
|
|
59
|
+
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
60
|
+
* expanded); each node contributes at most one √N page of containers;
|
|
61
|
+
* √N collected candidates decide. */
|
|
62
|
+
export declare function junctionContainersFrom(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, leftSeeds: number[], rightSeeds: number[],
|
|
63
|
+
/** Shared expansion budget — a TIER's √N pops, not each walk's, when one
|
|
64
|
+
* tier issues several walks (synonym junctions try up to 2·haloQueryK
|
|
65
|
+
* siblings; without a shared budget each sibling would spend its own √N). */
|
|
66
|
+
budget?: {
|
|
67
|
+
n: number;
|
|
68
|
+
},
|
|
69
|
+
/** ORDER-FREE containment: also accept containers holding right-then-left.
|
|
70
|
+
* A junction is evidence that the two forms were LEARNT TOGETHER; which
|
|
71
|
+
* one the query happened to mention first is a fact about the query, not
|
|
72
|
+
* about the learnt whole. The walk is identical (the seed ascent does not
|
|
73
|
+
* depend on order) — only the byte-containment test gains a second probe,
|
|
74
|
+
* so order-freedom costs two indexOf calls per visited node, never a
|
|
75
|
+
* second walk. */
|
|
76
|
+
unordered?: boolean): Junction[];
|
|
77
|
+
/** Tier 1 entry point: every learnt whole that literally contains
|
|
78
|
+
* left-then-right, found by ascending the structural DAG (parents +
|
|
79
|
+
* containment links) from the two sides' content-addressed identities.
|
|
80
|
+
* Both sides' seeds resolved fresh, one call. */
|
|
81
|
+
export declare function junctionContainers(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, unordered?: boolean): Junction[];
|
|
82
|
+
/** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
|
|
83
|
+
* halo siblings of left and right. When a distributional synonym of one
|
|
84
|
+
* form participates in a learnt whole with the other form, the container
|
|
85
|
+
* between the synonym and the other side is valid evidence for the
|
|
86
|
+
* original pair. The container evidence is exact (content-addressed DAG
|
|
87
|
+
* ascent, with window-id-enhanced seeds so non-W-aligned siblings still
|
|
88
|
+
* ascend); the relaxation is only in which form occupies one side — a
|
|
89
|
+
* distributional sibling rather than the exact form.
|
|
90
|
+
*
|
|
91
|
+
* ONE expansion budget is shared by every sibling walk in this call, so
|
|
92
|
+
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
93
|
+
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
94
|
+
* cannot be junction-sized). */
|
|
95
|
+
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean): Promise<Junction[]>;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// junction.ts — content-addressed junction search (bridge Tier 1).
|
|
2
|
+
//
|
|
3
|
+
// "Which learnt wholes ran L and R together?" answered by DAG ascent, not a
|
|
4
|
+
// similarity guess: hash-consing means any deposit containing L's bytes shares
|
|
5
|
+
// L's node (or L's canonical-window ids — position-independent identities), so
|
|
6
|
+
// climbing parents + containment links from L's and R's seeds reaches every
|
|
7
|
+
// container that literally holds L-then-R. A resonance seed (the gist of the
|
|
8
|
+
// bare concatenation — an object never learnt) could rank the true container
|
|
9
|
+
// out of its top-k; the ascent cannot.
|
|
10
|
+
//
|
|
11
|
+
// Extracted from resonance.ts so BOTH the bridge (a connector between two
|
|
12
|
+
// adjacent ANSWER pieces) and cross-region attention (the joint CONTEXT of two
|
|
13
|
+
// non-adjacent QUERY regions) ascend by the same disciplined, bounded walk.
|
|
14
|
+
import { read, resolve } from "./primitives.js";
|
|
15
|
+
import { windowIds } from "./canonical.js";
|
|
16
|
+
import { hubBound } from "./traverse.js";
|
|
17
|
+
import { haloSiblings } from "./match.js";
|
|
18
|
+
import { indexOf } from "../bytes.js";
|
|
19
|
+
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
20
|
+
* node when it is a stored form, plus — when the node has no structural
|
|
21
|
+
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
22
|
+
* parents, but its constituent W-grams typically do; the window ids
|
|
23
|
+
* provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
|
|
24
|
+
* single indexed lookup, far cheaper than computing every window id, so
|
|
25
|
+
* the window-id path is only taken when the node alone cannot ascend.
|
|
26
|
+
* Exported for callers (synonym junctions) that hold one side FIXED across
|
|
27
|
+
* several calls and so compute its seeds once instead of per call. */
|
|
28
|
+
export function junctionSeeds(ctx, b) {
|
|
29
|
+
const r = resolve(ctx, b);
|
|
30
|
+
if (r !== null) {
|
|
31
|
+
if (ctx.store.parentsFirst(r, 1).length > 0)
|
|
32
|
+
return [r];
|
|
33
|
+
const wids = [...windowIds(ctx, b).values()];
|
|
34
|
+
return [r, ...wids];
|
|
35
|
+
}
|
|
36
|
+
const wids = [...windowIds(ctx, b).values()];
|
|
37
|
+
if (wids.length <= 2)
|
|
38
|
+
return wids;
|
|
39
|
+
return [wids[0], wids[wids.length - 1]];
|
|
40
|
+
}
|
|
41
|
+
const walkCaches = new WeakMap();
|
|
42
|
+
export function walkCache(ctx) {
|
|
43
|
+
if (ctx.climbMemo === null)
|
|
44
|
+
return null;
|
|
45
|
+
let c = walkCaches.get(ctx.climbMemo);
|
|
46
|
+
if (c === undefined) {
|
|
47
|
+
walkCaches.set(ctx.climbMemo, c = { reads: new Map(), parents: new Map(), containers: new Map() });
|
|
48
|
+
}
|
|
49
|
+
return c;
|
|
50
|
+
}
|
|
51
|
+
export function cachedRead(ctx, cache, id, cap) {
|
|
52
|
+
if (cache === null)
|
|
53
|
+
return read(ctx, id, cap + 1);
|
|
54
|
+
const hit = cache.reads.get(id);
|
|
55
|
+
// A cached COMPLETE read serves any cap; a cached truncated read serves
|
|
56
|
+
// any cap it already covers (the caller only checks `length > cap`).
|
|
57
|
+
if (hit !== undefined && (hit.complete || hit.b.length > cap))
|
|
58
|
+
return hit.b;
|
|
59
|
+
const b = read(ctx, id, cap + 1);
|
|
60
|
+
cache.reads.set(id, { b, complete: b.length <= cap });
|
|
61
|
+
return b;
|
|
62
|
+
}
|
|
63
|
+
function cachedParents(ctx, cache, id, limit) {
|
|
64
|
+
if (cache === null)
|
|
65
|
+
return ctx.store.parentsFirst(id, limit);
|
|
66
|
+
let v = cache.parents.get(id);
|
|
67
|
+
if (v === undefined) {
|
|
68
|
+
v = ctx.store.parentsFirst(id, limit);
|
|
69
|
+
cache.parents.set(id, v);
|
|
70
|
+
}
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
function cachedContainers(ctx, cache, id, limit) {
|
|
74
|
+
if (cache === null)
|
|
75
|
+
return ctx.store.containersSlice(id, 0, limit);
|
|
76
|
+
let v = cache.containers.get(id);
|
|
77
|
+
if (v === undefined) {
|
|
78
|
+
v = ctx.store.containersSlice(id, 0, limit);
|
|
79
|
+
cache.containers.set(id, v);
|
|
80
|
+
}
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
/** Tier 1 body, parameterised on already-resolved seed lists so a caller
|
|
84
|
+
* holding one side FIXED across several calls (synonym junctions) pays for
|
|
85
|
+
* that side's seeds once, not once per call. The byte-containment check
|
|
86
|
+
* below ensures only genuine containers are returned regardless of seeds.
|
|
87
|
+
*
|
|
88
|
+
* BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
|
|
89
|
+
* store, where the unbounded form spent >90% of a query's CPU here):
|
|
90
|
+
* • PHRASE-SCALE READS — a junction container is by contract a whole the
|
|
91
|
+
* pair nearly exhausts (glue from a period to a phrase), so every visit
|
|
92
|
+
* reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
|
|
93
|
+
* A node whose bytes exceed the cap cannot be a junction container, and
|
|
94
|
+
* its ancestors are strictly larger — the branch is PRUNED, never
|
|
95
|
+
* reconstructing a corpus-sized deposit (an oasst2 conversation) just
|
|
96
|
+
* to reject it.
|
|
97
|
+
* • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
|
|
98
|
+
* frontier (the one fan-out convention) through the ~W structural
|
|
99
|
+
* levels that separate phrase-scale content from its containers
|
|
100
|
+
* (perception trees are W-ary, so a junction container lies within a
|
|
101
|
+
* few levels of its parts). A side too common to decide within the
|
|
102
|
+
* budget abstains here and falls through to the resonance tier (the
|
|
103
|
+
* climb's own saturation semantics).
|
|
104
|
+
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
105
|
+
* expanded); each node contributes at most one √N page of containers;
|
|
106
|
+
* √N collected candidates decide. */
|
|
107
|
+
export function junctionContainersFrom(ctx, left, right, maxContainer, leftSeeds, rightSeeds,
|
|
108
|
+
/** Shared expansion budget — a TIER's √N pops, not each walk's, when one
|
|
109
|
+
* tier issues several walks (synonym junctions try up to 2·haloQueryK
|
|
110
|
+
* siblings; without a shared budget each sibling would spend its own √N). */
|
|
111
|
+
budget,
|
|
112
|
+
/** ORDER-FREE containment: also accept containers holding right-then-left.
|
|
113
|
+
* A junction is evidence that the two forms were LEARNT TOGETHER; which
|
|
114
|
+
* one the query happened to mention first is a fact about the query, not
|
|
115
|
+
* about the learnt whole. The walk is identical (the seed ascent does not
|
|
116
|
+
* depend on order) — only the byte-containment test gains a second probe,
|
|
117
|
+
* so order-freedom costs two indexOf calls per visited node, never a
|
|
118
|
+
* second walk. */
|
|
119
|
+
unordered = false) {
|
|
120
|
+
const bound = hubBound(ctx);
|
|
121
|
+
const joinedLength = left.length + right.length;
|
|
122
|
+
const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
|
|
123
|
+
if (seeds.length === 0)
|
|
124
|
+
return [];
|
|
125
|
+
const b = budget ?? { n: bound * ctx.space.maxGroup };
|
|
126
|
+
// DEPTH CAP: perception trees are W-ary and a junction container is
|
|
127
|
+
// phrase-scale, so it sits within ~log_W(maxContainer) structural levels
|
|
128
|
+
// of its parts — at most W levels for any practical W (plus the
|
|
129
|
+
// containment hop the seeds already are). Ancestry beyond that depth is
|
|
130
|
+
// strictly larger than any admissible container; walking it can only burn
|
|
131
|
+
// budget, never find a junction.
|
|
132
|
+
const maxDepth = ctx.space.maxGroup;
|
|
133
|
+
const out = [];
|
|
134
|
+
const cache = walkCache(ctx);
|
|
135
|
+
const seen = new Set(seeds);
|
|
136
|
+
const stack = seeds.map((id) => ({
|
|
137
|
+
id,
|
|
138
|
+
d: 0,
|
|
139
|
+
}));
|
|
140
|
+
while (stack.length > 0 && out.length < bound && b.n-- > 0) {
|
|
141
|
+
const { id: x, d } = stack.pop();
|
|
142
|
+
const f = cachedRead(ctx, cache, x, maxContainer);
|
|
143
|
+
if (f.length > maxContainer)
|
|
144
|
+
continue; // beyond phrase scale: prune branch
|
|
145
|
+
if (unordered) {
|
|
146
|
+
// Order-free containment does NOT require disjoint occurrences: two
|
|
147
|
+
// grid-aligned fragments of the same whole legitimately OVERLAP inside
|
|
148
|
+
// it ("red " at 0 and " cir" at 3 in `red circle`), and both being
|
|
149
|
+
// literal substrings is the evidence. The interior is the gap between
|
|
150
|
+
// them when they are disjoint, empty otherwise. Only the containment
|
|
151
|
+
// test differs from the ordered form — and because occurrences may
|
|
152
|
+
// overlap or abut, `f.length > joinedLength` is too strict (grid
|
|
153
|
+
// fragments of one whole sum past it; "red " + "circle" exactly equals
|
|
154
|
+
// `red circle`). The container must be a STRICT super-form of each
|
|
155
|
+
// side, so that holding both is more than restating either.
|
|
156
|
+
const li = indexOf(f, left, 0);
|
|
157
|
+
const ri = li >= 0 ? indexOf(f, right, 0) : -1;
|
|
158
|
+
if (li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)) {
|
|
159
|
+
const lo = Math.min(li + left.length, ri + right.length);
|
|
160
|
+
const hi = Math.max(li, ri);
|
|
161
|
+
out.push({
|
|
162
|
+
id: x,
|
|
163
|
+
interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (f.length > joinedLength) {
|
|
168
|
+
const li = indexOf(f, left, 0);
|
|
169
|
+
if (li >= 0) {
|
|
170
|
+
const ri = indexOf(f, right, li + left.length);
|
|
171
|
+
if (ri >= 0) {
|
|
172
|
+
out.push({ id: x, interior: f.subarray(li + left.length, ri) });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (d >= maxDepth)
|
|
177
|
+
continue; // deeper ancestry is beyond phrase scale
|
|
178
|
+
const parents = cachedParents(ctx, cache, x, bound + 1);
|
|
179
|
+
if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
|
|
180
|
+
for (const p of parents) {
|
|
181
|
+
if (!seen.has(p)) {
|
|
182
|
+
seen.add(p);
|
|
183
|
+
stack.push({ id: p, d: d + 1 });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Containment fan-out under the SAME hub reading as parents: a node
|
|
188
|
+
// whose containers fill a whole √N page is COMMON content — its
|
|
189
|
+
// containment ancestry reaches a non-discriminative slice of the corpus
|
|
190
|
+
// (the climb's saturation semantics), and walking it would spend the
|
|
191
|
+
// entire budget discovering nothing a junction could use. Such a node
|
|
192
|
+
// is not expanded through containment; a pair whose sides are common
|
|
193
|
+
// abstains here in a handful of pops and falls through to the resonance
|
|
194
|
+
// tier. Below the page bound the read IS the full container list, so
|
|
195
|
+
// the walk stays exact exactly where identity evidence discriminates.
|
|
196
|
+
const containers = cachedContainers(ctx, cache, x, bound);
|
|
197
|
+
if (containers.length < bound) {
|
|
198
|
+
for (const c of containers) {
|
|
199
|
+
if (!seen.has(c)) {
|
|
200
|
+
seen.add(c);
|
|
201
|
+
stack.push({ id: c, d: d + 1 });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
/** Tier 1 entry point: every learnt whole that literally contains
|
|
209
|
+
* left-then-right, found by ascending the structural DAG (parents +
|
|
210
|
+
* containment links) from the two sides' content-addressed identities.
|
|
211
|
+
* Both sides' seeds resolved fresh, one call. */
|
|
212
|
+
export function junctionContainers(ctx, left, right, maxContainer, unordered = false) {
|
|
213
|
+
return junctionContainersFrom(ctx, left, right, maxContainer, junctionSeeds(ctx, left), junctionSeeds(ctx, right), undefined, unordered);
|
|
214
|
+
}
|
|
215
|
+
/** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
|
|
216
|
+
* halo siblings of left and right. When a distributional synonym of one
|
|
217
|
+
* form participates in a learnt whole with the other form, the container
|
|
218
|
+
* between the synonym and the other side is valid evidence for the
|
|
219
|
+
* original pair. The container evidence is exact (content-addressed DAG
|
|
220
|
+
* ascent, with window-id-enhanced seeds so non-W-aligned siblings still
|
|
221
|
+
* ascend); the relaxation is only in which form occupies one side — a
|
|
222
|
+
* distributional sibling rather than the exact form.
|
|
223
|
+
*
|
|
224
|
+
* ONE expansion budget is shared by every sibling walk in this call, so
|
|
225
|
+
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
226
|
+
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
227
|
+
* cannot be junction-sized). */
|
|
228
|
+
export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false) {
|
|
229
|
+
const out = [];
|
|
230
|
+
const lId = resolve(ctx, left);
|
|
231
|
+
const rId = resolve(ctx, right);
|
|
232
|
+
if (lId === null && rId === null)
|
|
233
|
+
return out;
|
|
234
|
+
const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
|
|
235
|
+
// Left-side synonyms: containers of sibling+right. `right`'s seeds are
|
|
236
|
+
// FIXED across every sibling this loop tries.
|
|
237
|
+
if (lId !== null) {
|
|
238
|
+
const rightSeeds = junctionSeeds(ctx, right);
|
|
239
|
+
for (const sib of await haloSiblings(ctx, lId)) {
|
|
240
|
+
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
241
|
+
if (sibBytes.length === 0 || sibBytes.length > maxInterior)
|
|
242
|
+
continue;
|
|
243
|
+
const containers = junctionContainersFrom(ctx, sibBytes, right, sibBytes.length + right.length + maxInterior, junctionSeeds(ctx, sibBytes), rightSeeds, budget, unordered);
|
|
244
|
+
for (const c of containers)
|
|
245
|
+
out.push(c);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Right-side synonyms: containers of left+sibling. `left`'s seeds are
|
|
249
|
+
// likewise fixed across this loop.
|
|
250
|
+
if (rId !== null) {
|
|
251
|
+
const leftSeeds = junctionSeeds(ctx, left);
|
|
252
|
+
for (const sib of await haloSiblings(ctx, rId)) {
|
|
253
|
+
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
254
|
+
if (sibBytes.length === 0 || sibBytes.length > maxInterior)
|
|
255
|
+
continue;
|
|
256
|
+
const containers = junctionContainersFrom(ctx, left, sibBytes, left.length + sibBytes.length + maxInterior, leftSeeds, junctionSeeds(ctx, sibBytes), budget, unordered);
|
|
257
|
+
for (const c of containers)
|
|
258
|
+
out.push(c);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Sema } from "../sema.js";
|
|
2
|
+
import type { Input, MindContext } from "./types.js";
|
|
3
|
+
/** Intern a perceived tree into node ids, bottom-up, sharing equal subtrees.
|
|
4
|
+
* Returns the root node id and a map from tree nodes to their ids.
|
|
5
|
+
*
|
|
6
|
+
* Memoized by NODE IDENTITY (ctx._internIds): the pyramid fold shares a
|
|
7
|
+
* prefix's subtree OBJECTS across an accumulated context's deposits, and a
|
|
8
|
+
* node already interned needs nothing again — its id is permanent
|
|
9
|
+
* (content-addressed) and its intern-time side effects (gist capture, kid
|
|
10
|
+
* rows) fired at first mint; re-interning was pure lookups. A memo hit
|
|
11
|
+
* therefore skips the WHOLE shared subtree, making the intern walk
|
|
12
|
+
* O(new nodes) per deposit instead of O(context). Only the hit node
|
|
13
|
+
* itself enters `ids`; descendants stay reachable via the memo (see
|
|
14
|
+
* idOf in indexSubSpans and the changedNodes prune). */
|
|
15
|
+
export declare function internTreeIds(ctx: MindContext, node: Sema, ids: Map<Sema, number>): Promise<number>;
|
|
16
|
+
/** Index flat branches for sub-spans of a deposit's byte stream, linked to
|
|
17
|
+
* their structural chunks via durable CONTAINMENT edges. */
|
|
18
|
+
export declare function indexSubSpans(ctx: MindContext, tree: Sema, ids: Map<Sema, number>): Promise<boolean>;
|
|
19
|
+
/** Perceive, intern, and index a single input. Returns the perceived tree,
|
|
20
|
+
* root id, id map, and the changed (new) subtrees for halo reinforcement. */
|
|
21
|
+
export declare function deposit(ctx: MindContext, input: Input, track: boolean): Promise<{
|
|
22
|
+
tree: Sema;
|
|
23
|
+
rootId: number;
|
|
24
|
+
ids: Map<Sema, number>;
|
|
25
|
+
changed: Sema[];
|
|
26
|
+
}>;
|
|
27
|
+
/** Ingest a single input (a bare experience, no continuation). */
|
|
28
|
+
export declare function ingestOne(ctx: MindContext, input: Input): Promise<Sema & {
|
|
29
|
+
id: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** Ingest a pair (context, continuation) — learn an edge and pour halos. */
|
|
32
|
+
export declare function ingestPair(ctx: MindContext, ctxInput: Input, cont: Input): Promise<void>;
|
|
33
|
+
/** Dispatch the public ingest input shapes onto one-input / pair handlers —
|
|
34
|
+
* THE one reading of ingest's polymorphic surface (scalar, (context,
|
|
35
|
+
* continuation) pair, or a list mixing bare inputs and pairs). Both ingest
|
|
36
|
+
* paths — the direct one below and {@link CachedIngest} — route through
|
|
37
|
+
* this, so the shape-detection can never drift between them again (the
|
|
38
|
+
* ingest cache once re-implemented it and drifted). */
|
|
39
|
+
export declare function dispatchIngest(input: Input | (Input | [Input, Input])[], second: Input | undefined, onOne: (input: Input) => Promise<Sema & {
|
|
40
|
+
id: number;
|
|
41
|
+
}>, onPair: (ctxInput: Input, cont: Input) => Promise<void>): Promise<(Sema & {
|
|
42
|
+
id: number;
|
|
43
|
+
}) | undefined>;
|
|
44
|
+
/** Ingest an input or array of inputs/pairs. The public ingest entry point. */
|
|
45
|
+
export declare function ingest(ctx: MindContext, input: Input | (Input | [Input, Input])[], second?: Input): Promise<(Sema & {
|
|
46
|
+
id: number;
|
|
47
|
+
}) | undefined>;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// learning.ts — ingest and deposition (Section 7 of the mind).
|
|
2
|
+
//
|
|
3
|
+
// Learning is DEPOSITION: perceive a stream into a tree and intern every
|
|
4
|
+
// node. A fact is an EDGE between node ids; recall traverses edges.
|
|
5
|
+
import { bindSeat, companySignature, isChunk } from "../sema.js";
|
|
6
|
+
import { changedNodes } from "./types.js";
|
|
7
|
+
import { inputBytes, perceiveDeposit, resolve, } from "./primitives.js";
|
|
8
|
+
import { canonicalWindows, leafIdPrefix } from "./canonical.js";
|
|
9
|
+
import { fold as foldVecs } from "../sema.js";
|
|
10
|
+
/** Intern a perceived tree into node ids, bottom-up, sharing equal subtrees.
|
|
11
|
+
* Returns the root node id and a map from tree nodes to their ids.
|
|
12
|
+
*
|
|
13
|
+
* Memoized by NODE IDENTITY (ctx._internIds): the pyramid fold shares a
|
|
14
|
+
* prefix's subtree OBJECTS across an accumulated context's deposits, and a
|
|
15
|
+
* node already interned needs nothing again — its id is permanent
|
|
16
|
+
* (content-addressed) and its intern-time side effects (gist capture, kid
|
|
17
|
+
* rows) fired at first mint; re-interning was pure lookups. A memo hit
|
|
18
|
+
* therefore skips the WHOLE shared subtree, making the intern walk
|
|
19
|
+
* O(new nodes) per deposit instead of O(context). Only the hit node
|
|
20
|
+
* itself enters `ids`; descendants stay reachable via the memo (see
|
|
21
|
+
* idOf in indexSubSpans and the changedNodes prune). */
|
|
22
|
+
export async function internTreeIds(ctx, node, ids) {
|
|
23
|
+
const known = ctx._internIds.get(node);
|
|
24
|
+
if (known !== undefined) {
|
|
25
|
+
ids.set(node, known);
|
|
26
|
+
return known;
|
|
27
|
+
}
|
|
28
|
+
let id;
|
|
29
|
+
if (node.kids === null) {
|
|
30
|
+
id = await ctx.store.putLeaf(node.leaf ?? new Uint8Array(0), node.v);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const kds = [];
|
|
34
|
+
for (const k of node.kids)
|
|
35
|
+
kds.push(await internTreeIds(ctx, k, ids));
|
|
36
|
+
id = await ctx.store.putBranch(kds, node.v);
|
|
37
|
+
}
|
|
38
|
+
ids.set(node, id);
|
|
39
|
+
ctx._internIds.set(node, id);
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
/** Index flat branches for sub-spans of a deposit's byte stream, linked to
|
|
43
|
+
* their structural chunks via durable CONTAINMENT edges. */
|
|
44
|
+
export async function indexSubSpans(ctx, tree, ids) {
|
|
45
|
+
const chunkOf = [];
|
|
46
|
+
const streamIds = [];
|
|
47
|
+
const streamVecs = [];
|
|
48
|
+
const collect = (n) => {
|
|
49
|
+
if (isChunk(n)) {
|
|
50
|
+
// A chunk inside a memo-skipped shared subtree is absent from `ids`;
|
|
51
|
+
// the intern memo still knows it (same object). A miss on both (the
|
|
52
|
+
// WeakMap entry was collected) only forfeits the seenBefore skip.
|
|
53
|
+
const chunkId = ids.get(n) ?? ctx._internIds.get(n);
|
|
54
|
+
for (const k of n.kids) {
|
|
55
|
+
const lid = k.leaf ? ctx.store.findLeaf(k.leaf) : null;
|
|
56
|
+
if (lid === null)
|
|
57
|
+
return false;
|
|
58
|
+
streamIds.push(lid);
|
|
59
|
+
streamVecs.push(k.v);
|
|
60
|
+
chunkOf.push(chunkId);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (n.kids) {
|
|
65
|
+
for (const k of n.kids)
|
|
66
|
+
if (!collect(k))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
if (!collect(tree))
|
|
72
|
+
return false;
|
|
73
|
+
const W = ctx.space.maxGroup; // write side of the canonical contract
|
|
74
|
+
const prev = ctx._prevSeen;
|
|
75
|
+
const seenBefore = (off, len) => {
|
|
76
|
+
if (!prev)
|
|
77
|
+
return false;
|
|
78
|
+
for (let i = off; i < off + len; i++) {
|
|
79
|
+
const c = chunkOf[i];
|
|
80
|
+
if (c === undefined || !prev.has(c))
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
const lens = streamIds.length >= W ? canonicalWindows(W) : [streamIds.length];
|
|
86
|
+
for (const len of lens) {
|
|
87
|
+
if (len < 1)
|
|
88
|
+
continue;
|
|
89
|
+
for (let off = 0; off + len <= streamIds.length; off++) {
|
|
90
|
+
if (seenBefore(off, len))
|
|
91
|
+
continue;
|
|
92
|
+
const winIds = streamIds.slice(off, off + len);
|
|
93
|
+
const flatId = ctx.store.findBranch(winIds) ??
|
|
94
|
+
await ctx.store.putBranch(winIds, foldVecs(ctx.space, streamVecs.slice(off, off + len)));
|
|
95
|
+
for (let i = off; i < off + len; i++) {
|
|
96
|
+
const c = chunkOf[i];
|
|
97
|
+
if (c !== undefined)
|
|
98
|
+
ctx.store.addContainer(flatId, c);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
/** Perceive, intern, and index a single input. Returns the perceived tree,
|
|
105
|
+
* root id, id map, and the changed (new) subtrees for halo reinforcement. */
|
|
106
|
+
export async function deposit(ctx, input, track) {
|
|
107
|
+
const bytes = inputBytes(ctx, input);
|
|
108
|
+
// Deposit-shaped perception: stable-prefix tree SEEDING (see
|
|
109
|
+
// perceiveDeposit) — an accumulated context re-folds only its new suffix,
|
|
110
|
+
// O(turn) instead of O(context) per conversation turn. Cache-only here
|
|
111
|
+
// (no store-probe fallback): a knownPrefixLength scan on every novel fact
|
|
112
|
+
// would cost O(n²) hashing, while conversation replays are always warm —
|
|
113
|
+
// re-deposition replays from the first turn, rebuilding the cache as it
|
|
114
|
+
// goes.
|
|
115
|
+
const tree = perceiveDeposit(ctx, bytes);
|
|
116
|
+
const ids = new Map();
|
|
117
|
+
const rootId = await internTreeIds(ctx, tree, ids);
|
|
118
|
+
const indexed = await indexSubSpans(ctx, tree, ids);
|
|
119
|
+
const leafIds = leafIdPrefix(ctx, bytes);
|
|
120
|
+
if (leafIds.length === bytes.length && leafIds.length >= 2) {
|
|
121
|
+
await ctx.store.putBranch(leafIds, tree.v);
|
|
122
|
+
}
|
|
123
|
+
const changed = (track && ctx._prevSeen)
|
|
124
|
+
? changedNodes(tree, ids, ctx._prevSeen)
|
|
125
|
+
: [tree];
|
|
126
|
+
if (track)
|
|
127
|
+
ctx._prevSeen = indexed ? new Set(ids.values()) : null;
|
|
128
|
+
return { tree, rootId, ids, changed };
|
|
129
|
+
}
|
|
130
|
+
/** Ingest a single input (a bare experience, no continuation). */
|
|
131
|
+
export async function ingestOne(ctx, input) {
|
|
132
|
+
const { tree, rootId, ids } = await deposit(ctx, input, true);
|
|
133
|
+
ctx.store.indexTarget(rootId);
|
|
134
|
+
const parts = tree.kids
|
|
135
|
+
? tree.kids.map((k) => ids.get(k))
|
|
136
|
+
: [rootId];
|
|
137
|
+
const stride = ctx.space.maxGroup;
|
|
138
|
+
if (parts.length > stride) {
|
|
139
|
+
for (let i = 0; i + stride < parts.length; i += stride) {
|
|
140
|
+
await ctx.store.link(parts[i], parts[i + stride]);
|
|
141
|
+
}
|
|
142
|
+
if ((parts.length - 1) % stride !== 0) {
|
|
143
|
+
const lastStart = Math.floor((parts.length - 1) / stride) * stride;
|
|
144
|
+
if (lastStart < parts.length - 1) {
|
|
145
|
+
await ctx.store.link(parts[lastStart], parts[parts.length - 1]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
for (const id of parts)
|
|
151
|
+
ctx.store.indexTarget(id);
|
|
152
|
+
}
|
|
153
|
+
return Object.assign(tree, { id: rootId });
|
|
154
|
+
}
|
|
155
|
+
/** For each right-edge suffix of the context bytes, resolve it against the
|
|
156
|
+
* store. A suffix whose resolved node is already a known form inherits the
|
|
157
|
+
* continuation edge. Gate: ≥ 2 structural parents (reused across deposits),
|
|
158
|
+
* or (halo > 0 ∧ already an edge source). Pure answers do not qualify. */
|
|
159
|
+
async function propagateSuffixes(ctx, src, dst) {
|
|
160
|
+
const W = ctx.space.maxGroup;
|
|
161
|
+
const bytes = ctx.store.bytes(src);
|
|
162
|
+
const n = bytes.length;
|
|
163
|
+
if (n < 2 * W)
|
|
164
|
+
return;
|
|
165
|
+
// Existence prefilter — the write side of the canonical contract: every
|
|
166
|
+
// deposit interns its WHOLE byte stream as a flat branch of per-byte leaf
|
|
167
|
+
// ids (deposit(), canonical.ts). A suffix is a stored form exactly when
|
|
168
|
+
// that flat twin exists, so one content-hash probe per offset decides;
|
|
169
|
+
// only a hit pays for resolve()'s deposit-shaped perception. This keeps
|
|
170
|
+
// the scan free of river folds — O(1) probes over cheap byte hashes
|
|
171
|
+
// instead of O(suffix) vector folds per offset.
|
|
172
|
+
const leafIds = leafIdPrefix(ctx, bytes);
|
|
173
|
+
for (let i = 1; i <= n - W; i++) {
|
|
174
|
+
if (ctx.store.findBranch(leafIds.slice(i)) === null)
|
|
175
|
+
continue;
|
|
176
|
+
const id = resolve(ctx, bytes.subarray(i));
|
|
177
|
+
if (id === null || id === src)
|
|
178
|
+
continue;
|
|
179
|
+
const known = ctx.store.parentsFirst(id, 2).length >= 2 ||
|
|
180
|
+
(ctx.store.haloMass(id) > 0 && ctx.store.hasNext(id));
|
|
181
|
+
if (!known)
|
|
182
|
+
continue;
|
|
183
|
+
await ctx.store.link(id, dst);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Ingest a pair (context, continuation) — learn an edge and pour halos. */
|
|
187
|
+
export async function ingestPair(ctx, ctxInput, cont) {
|
|
188
|
+
const c = await deposit(ctx, ctxInput, true);
|
|
189
|
+
const cont_ = await deposit(ctx, cont, false);
|
|
190
|
+
const ctxId = c.rootId, contId = cont_.rootId;
|
|
191
|
+
await ctx.store.link(ctxId, contId);
|
|
192
|
+
await propagateSuffixes(ctx, ctxId, contId);
|
|
193
|
+
// Halos pour company SIGNATURES (identity), not gists (content) — see
|
|
194
|
+
// companySignature in sema.ts.
|
|
195
|
+
const contSeat = bindSeat(ctx.space, companySignature(ctx.space, contId), 1);
|
|
196
|
+
for (const part of c.changed) {
|
|
197
|
+
const partId = c.ids.get(part);
|
|
198
|
+
await ctx.store.pourHalo(partId, contSeat);
|
|
199
|
+
await ctx.store.pourHalo(contId, bindSeat(ctx.space, companySignature(ctx.space, partId), 0));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Dispatch the public ingest input shapes onto one-input / pair handlers —
|
|
203
|
+
* THE one reading of ingest's polymorphic surface (scalar, (context,
|
|
204
|
+
* continuation) pair, or a list mixing bare inputs and pairs). Both ingest
|
|
205
|
+
* paths — the direct one below and {@link CachedIngest} — route through
|
|
206
|
+
* this, so the shape-detection can never drift between them again (the
|
|
207
|
+
* ingest cache once re-implemented it and drifted). */
|
|
208
|
+
export async function dispatchIngest(input, second, onOne, onPair) {
|
|
209
|
+
if (Array.isArray(input) && !(input instanceof Uint8Array) &&
|
|
210
|
+
input.width === undefined) {
|
|
211
|
+
const arr = input;
|
|
212
|
+
if (arr.length === 2 && !Array.isArray(arr[0]) && !Array.isArray(arr[1])) {
|
|
213
|
+
await onPair(arr[0], arr[1]);
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
for (const item of arr) {
|
|
217
|
+
if (Array.isArray(item) && item.length === 2) {
|
|
218
|
+
await onPair(item[0], item[1]);
|
|
219
|
+
}
|
|
220
|
+
else
|
|
221
|
+
await onOne(item);
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
if (second === undefined)
|
|
226
|
+
return onOne(input);
|
|
227
|
+
await onPair(input, second);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
/** Ingest an input or array of inputs/pairs. The public ingest entry point. */
|
|
231
|
+
export async function ingest(ctx, input, second) {
|
|
232
|
+
return dispatchIngest(input, second, (i) => ingestOne(ctx, i), (a, b) => ingestPair(ctx, a, b));
|
|
233
|
+
}
|