pilotswarm-horizon-store 0.2.2
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/README.md +154 -0
- package/dist/src/agent-tools.d.ts +32 -0
- package/dist/src/agent-tools.d.ts.map +1 -0
- package/dist/src/agent-tools.js +263 -0
- package/dist/src/agent-tools.js.map +1 -0
- package/dist/src/config.d.ts +93 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +120 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/db-retry.d.ts +21 -0
- package/dist/src/db-retry.d.ts.map +1 -0
- package/dist/src/db-retry.js +94 -0
- package/dist/src/db-retry.js.map +1 -0
- package/dist/src/embedding-client.d.ts +13 -0
- package/dist/src/embedding-client.d.ts.map +1 -0
- package/dist/src/embedding-client.js +79 -0
- package/dist/src/embedding-client.js.map +1 -0
- package/dist/src/graph-model.d.ts +49 -0
- package/dist/src/graph-model.d.ts.map +1 -0
- package/dist/src/graph-model.js +112 -0
- package/dist/src/graph-model.js.map +1 -0
- package/dist/src/graph-queries.d.ts +83 -0
- package/dist/src/graph-queries.d.ts.map +1 -0
- package/dist/src/graph-queries.js +727 -0
- package/dist/src/graph-queries.js.map +1 -0
- package/dist/src/graph-store.d.ts +57 -0
- package/dist/src/graph-store.d.ts.map +1 -0
- package/dist/src/graph-store.js +327 -0
- package/dist/src/graph-store.js.map +1 -0
- package/dist/src/horizon-migrator.d.ts +49 -0
- package/dist/src/horizon-migrator.d.ts.map +1 -0
- package/dist/src/horizon-migrator.js +231 -0
- package/dist/src/horizon-migrator.js.map +1 -0
- package/dist/src/horizon-store.d.ts +116 -0
- package/dist/src/horizon-store.d.ts.map +1 -0
- package/dist/src/horizon-store.js +563 -0
- package/dist/src/horizon-store.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/preconditions.d.ts +16 -0
- package/dist/src/preconditions.d.ts.map +1 -0
- package/dist/src/preconditions.js +99 -0
- package/dist/src/preconditions.js.map +1 -0
- package/dist/src/query-builder.d.ts +37 -0
- package/dist/src/query-builder.d.ts.map +1 -0
- package/dist/src/query-builder.js +82 -0
- package/dist/src/query-builder.js.map +1 -0
- package/dist/src/sql-util.d.ts +20 -0
- package/dist/src/sql-util.d.ts.map +1 -0
- package/dist/src/sql-util.js +40 -0
- package/dist/src/sql-util.js.map +1 -0
- package/dist/src/types.d.ts +16 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +35 -0
- package/dist/src/types.js.map +1 -0
- package/migrations/0001_facts_table.sql +77 -0
- package/migrations/0002_indexes.sql +27 -0
- package/migrations/0003_age_bootstrap.sql +13 -0
- package/migrations/0004_facts_procs.sql +253 -0
- package/migrations/0005_embedder_workflow.sql +119 -0
- package/migrations/0006_facts_read_uncrawled_embedded_gate.sql +33 -0
- package/migrations/0007_embed_key_value_text.sql +122 -0
- package/migrations/0008_embedder_failure_recovery.sql +409 -0
- package/migrations/0009_search_text_full_value.sql +52 -0
- package/migrations/0010_minimal_two_loop_embedder.sql +392 -0
- package/migrations/0011_unified_api_embedder_workflow.sql +273 -0
- package/migrations/0012_facts_soft_delete.sql +587 -0
- package/migrations/0013_graph_namespaces.sql +60 -0
- package/package.json +69 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — provider configuration.
|
|
2
|
+
//
|
|
3
|
+
// The EnhancedFactStore is configured with a connection string and, optionally,
|
|
4
|
+
// an EMBEDDING ENDPOINT. The embedding endpoint is a DATABASE-AGNOSTIC contract:
|
|
5
|
+
// it describes an OpenAI/Azure-OpenAI-compatible HTTP embeddings API and says
|
|
6
|
+
// nothing about how any particular provider invokes it. A provider records this
|
|
7
|
+
// endpoint and embeds facts however it likes — the HorizonDB provider calls it
|
|
8
|
+
// in-database via pg_durable's df.http() background loop, but an AlloyDB- or
|
|
9
|
+
// any-Postgres-based provider could consume the exact same config and call it
|
|
10
|
+
// from a trigger, a sidecar, or host-side code. PilotSwarm only passes the
|
|
11
|
+
// endpoint; the mechanism is the provider's concern.
|
|
12
|
+
//
|
|
13
|
+
// The SAME endpoint config is also usable Node-side (see embedding-client.ts)
|
|
14
|
+
// for query-time embedding and for tests that don't depend on any in-database
|
|
15
|
+
// HTTP path.
|
|
16
|
+
export const DEFAULT_SCHEMA = "horizon_facts";
|
|
17
|
+
export const DEFAULT_GRAPH = "horizon_facts";
|
|
18
|
+
export const DEFAULT_POOL_MAX = 16;
|
|
19
|
+
export const DEFAULT_CONNECTION_TIMEOUT_MS = 15_000;
|
|
20
|
+
export const DEFAULT_NAMESPACE_CACHE_TTL_MS = 60_000;
|
|
21
|
+
function resolveConnectionTimeoutMillis() {
|
|
22
|
+
const raw = process.env.HORIZON_CONNECTION_TIMEOUT_MS;
|
|
23
|
+
if (raw == null || raw === "")
|
|
24
|
+
return DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
25
|
+
const value = Number(raw);
|
|
26
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
27
|
+
throw new Error(`HORIZON_CONNECTION_TIMEOUT_MS must be a non-negative number, got ${JSON.stringify(raw)}`);
|
|
28
|
+
}
|
|
29
|
+
return Math.trunc(value);
|
|
30
|
+
}
|
|
31
|
+
/** Resolve + validate the namespace cache TTL (ms). Rejects NaN / negative. */
|
|
32
|
+
function resolveNamespaceCacheTtl(explicit) {
|
|
33
|
+
const envRaw = process.env.HORIZON_NAMESPACE_CACHE_TTL_MS;
|
|
34
|
+
const raw = explicit ?? (envRaw != null && envRaw !== "" ? Number(envRaw) : undefined);
|
|
35
|
+
if (raw == null)
|
|
36
|
+
return DEFAULT_NAMESPACE_CACHE_TTL_MS;
|
|
37
|
+
if (!Number.isFinite(raw) || raw < 0) {
|
|
38
|
+
throw new Error(`namespaceCacheTtlMs / HORIZON_NAMESPACE_CACHE_TTL_MS must be a non-negative number, got ${JSON.stringify(envRaw ?? explicit)}`);
|
|
39
|
+
}
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
42
|
+
/** Resolve config from explicit values, falling back to HORIZON_* env vars. */
|
|
43
|
+
export function resolveConfig(partial = {}) {
|
|
44
|
+
const connectionString = partial.connectionString ?? process.env.HORIZON_DATABASE_URL ?? "";
|
|
45
|
+
if (!connectionString) {
|
|
46
|
+
throw new Error("HorizonDBFactStore requires a connectionString (or HORIZON_DATABASE_URL env var).");
|
|
47
|
+
}
|
|
48
|
+
// The embedding endpoint must be passed EXPLICITLY (no env-var fallback):
|
|
49
|
+
// an implicit endpoint can silently conflict with the store's vector(N)
|
|
50
|
+
// dimension, and config provenance should be the host's choice (02 §1b).
|
|
51
|
+
const embedding = partial.embedding;
|
|
52
|
+
const graphName = partial.graphName ?? DEFAULT_GRAPH;
|
|
53
|
+
return {
|
|
54
|
+
connectionString,
|
|
55
|
+
schema: partial.schema ?? DEFAULT_SCHEMA,
|
|
56
|
+
graphName,
|
|
57
|
+
registrySchema: partial.registrySchema ?? process.env.HORIZON_GRAPH_REGISTRY_SCHEMA ?? `${graphName}_registry`,
|
|
58
|
+
embedding,
|
|
59
|
+
embeddingDim: partial.embeddingDim ?? embedding?.dim ?? 1536,
|
|
60
|
+
annIndex: partial.annIndex
|
|
61
|
+
?? process.env.HORIZON_ANN_INDEX
|
|
62
|
+
?? "auto",
|
|
63
|
+
poolMax: partial.poolMax
|
|
64
|
+
?? (process.env.HORIZON_POOL_MAX ? Number(process.env.HORIZON_POOL_MAX) : undefined)
|
|
65
|
+
?? DEFAULT_POOL_MAX,
|
|
66
|
+
namespaceCacheTtlMs: resolveNamespaceCacheTtl(partial.namespaceCacheTtlMs),
|
|
67
|
+
useManagedIdentity: partial.useManagedIdentity,
|
|
68
|
+
aadUser: partial.aadUser,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** sslmode values that mean "use TLS" (mirrors the PilotSwarm SDK pg-pool-factory). */
|
|
72
|
+
const SSL_REQUIRING_MODES = ["require", "prefer", "verify-ca", "verify-full"];
|
|
73
|
+
/**
|
|
74
|
+
* Build a `pg.PoolConfig` from a HorizonDB connection string, normalizing TLS
|
|
75
|
+
* the same way the PilotSwarm SDK's `pg-pool-factory` does for the CMS / facts
|
|
76
|
+
* pools, so a HorizonDB URL behaves here exactly like `DATABASE_URL` does there.
|
|
77
|
+
*
|
|
78
|
+
* Why this is needed: HorizonDB requires SSL, but its certificate chain is not in
|
|
79
|
+
* Node's default trust store, and `pg` v8 treats `sslmode=require` (and
|
|
80
|
+
* `prefer` / `verify-ca` / `verify-full`) as `verify-full` — which rejects the
|
|
81
|
+
* chain with `self-signed certificate in certificate chain`. The SDK factory
|
|
82
|
+
* handles this for `DATABASE_URL` by stripping `sslmode` from the URL and setting
|
|
83
|
+
* `ssl: { rejectUnauthorized: false }` on the pool config. This provider builds
|
|
84
|
+
* its OWN raw pools (it keeps its runtime dep surface to `pg` only and does not
|
|
85
|
+
* import the SDK), so without this helper a natural `?sslmode=require` URL fails
|
|
86
|
+
* here while the identical URL works for `DATABASE_URL` — the asymmetry that
|
|
87
|
+
* previously forced callers to hand-append `uselibpqcompat=true`.
|
|
88
|
+
*
|
|
89
|
+
* Encrypt-but-don't-verify matches libpq's `sslmode=require` semantics and the
|
|
90
|
+
* SDK's existing posture (this is not a new weakening — it is the same choice the
|
|
91
|
+
* CMS/facts pools already make). For full CA verification against a non-preview
|
|
92
|
+
* cluster, present a trusted chain and pass a URL without an `sslmode` param.
|
|
93
|
+
*/
|
|
94
|
+
export function buildPoolConfig(connectionString, max) {
|
|
95
|
+
let needsSsl = false;
|
|
96
|
+
let sanitized = connectionString;
|
|
97
|
+
try {
|
|
98
|
+
const url = new URL(connectionString);
|
|
99
|
+
needsSsl = SSL_REQUIRING_MODES.includes(url.searchParams.get("sslmode") ?? "");
|
|
100
|
+
if (needsSsl) {
|
|
101
|
+
// Control SSL via the config object, not the URL. Drop sslmode so pg
|
|
102
|
+
// does not re-apply verify-full, and drop the now-redundant
|
|
103
|
+
// uselibpqcompat hint so the effective URL is clean either way.
|
|
104
|
+
url.searchParams.delete("sslmode");
|
|
105
|
+
url.searchParams.delete("uselibpqcompat");
|
|
106
|
+
sanitized = url.toString();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Not a parseable URL (unexpected for a real DSN). Leave it untouched and
|
|
111
|
+
// let pg surface a clear connection error rather than masking it here.
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
connectionString: sanitized,
|
|
115
|
+
max,
|
|
116
|
+
connectionTimeoutMillis: resolveConnectionTimeoutMillis(),
|
|
117
|
+
...(needsSsl ? { ssl: { rejectUnauthorized: false } } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function isTransientDbError(err: any): boolean;
|
|
2
|
+
/** The AGE lazy-label-creation race, in BOTH manifestations (42P07 duplicate_table
|
|
3
|
+
* and 23505 unique_violation on a pg_catalog index). Re-running the same
|
|
4
|
+
* idempotent graph statement once the label exists succeeds. Label-agnostic. */
|
|
5
|
+
export declare function isLabelCreationRaceError(err: any): boolean;
|
|
6
|
+
export interface DbRetryHooks {
|
|
7
|
+
onRetry?: (info: {
|
|
8
|
+
label: string;
|
|
9
|
+
attempt: number;
|
|
10
|
+
error: any;
|
|
11
|
+
}) => void;
|
|
12
|
+
}
|
|
13
|
+
/** Install process-wide retry observers (used by the eval perf/error report). */
|
|
14
|
+
export declare function setDbRetryHooks(hooks: DbRetryHooks): void;
|
|
15
|
+
export interface DbRetryOpts {
|
|
16
|
+
tries?: number;
|
|
17
|
+
/** Which errors are retryable. Default: isTransientDbError (connection class). */
|
|
18
|
+
isRetryable?: (err: any) => boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function withDbRetry<T>(label: string, fn: () => Promise<T>, opts?: DbRetryOpts): Promise<T>;
|
|
21
|
+
//# sourceMappingURL=db-retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-retry.d.ts","sourceRoot":"","sources":["../../src/db-retry.ts"],"names":[],"mappings":"AAwDA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAIpD;AAED;;gFAEgF;AAChF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAS1D;AAED,MAAM,WAAW,YAAY;IACzB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,KAAK,IAAI,CAAC;CAC5E;AAGD,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAkC;AAE5F,MAAM,WAAW,WAAW;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAe5G"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — retry helpers (HorizonDB preview hardening).
|
|
2
|
+
//
|
|
3
|
+
// Two distinct retryable classes, each with its own classifier so callers opt
|
|
4
|
+
// into exactly the one that is safe for them:
|
|
5
|
+
//
|
|
6
|
+
// 1. TRANSIENT CONNECTION (isTransientDbError, the default):
|
|
7
|
+
// Azure HorizonDB (preview) intermittently drops pooled TLS connections —
|
|
8
|
+
// ECONNRESET / ENOTCONN / "Connection terminated" / "terminating
|
|
9
|
+
// connection", plus the pg connection-class SQLSTATEs (08xxx, 57P0x). These
|
|
10
|
+
// surface either as a rejected query or at connection acquisition; in both
|
|
11
|
+
// cases the statement did not partially commit, so re-running (on a FRESH
|
|
12
|
+
// connection) is safe. Never matches SQL/logic errors — those must surface.
|
|
13
|
+
// Also covers connection-ESTABLISHMENT failures that are transient under
|
|
14
|
+
// load: EADDRNOTAVAIL / ECONNABORTED (local socket exhaustion or aborted
|
|
15
|
+
// connection setup from rapid connection churn) and DNS hiccups (ENOTFOUND /
|
|
16
|
+
// EAI_AGAIN / getaddrinfo) against the cluster host — the query never ran,
|
|
17
|
+
// so a fresh-connection retry is safe.
|
|
18
|
+
//
|
|
19
|
+
// 2. AGE LABEL-CREATION RACE (isLabelCreationRaceError):
|
|
20
|
+
// Apache AGE creates a label's backing table LAZILY on the first
|
|
21
|
+
// CREATE/MERGE that references it. The label set is owned by the layer above
|
|
22
|
+
// us (the harvester / app choose node kinds + edge predicates), so we never
|
|
23
|
+
// enumerate it. When two writers race the first reference to the same label,
|
|
24
|
+
// the loser's internal label-table creation aborts the whole Cypher statement
|
|
25
|
+
// — so the MERGE/CREATE persisted nothing. The label now EXISTS, so re-running
|
|
26
|
+
// the SAME statement succeeds. This is label-agnostic by construction.
|
|
27
|
+
//
|
|
28
|
+
// The race has TWO observed manifestations (HorizonDB AGE 1.6.0), depending
|
|
29
|
+
// on WHERE the duplicate is detected:
|
|
30
|
+
// - 42P07 duplicate_table — `relation "<label>" already exists` (caught at
|
|
31
|
+
// the relation level), and
|
|
32
|
+
// - 23505 unique_violation on a SYSTEM-CATALOG index, e.g.
|
|
33
|
+
// `pg_class_relname_nsp_index` (two concurrent label-table creations race
|
|
34
|
+
// into pg_class before either registered). NOTE the "already exists"
|
|
35
|
+
// text is in err.detail, not err.message, so a message regex misses it —
|
|
36
|
+
// we key off the code + the catalog constraint name instead. We retry
|
|
37
|
+
// 23505 ONLY when the violated constraint is a pg_catalog index, never a
|
|
38
|
+
// user constraint (the graph has no user unique constraints anyway).
|
|
39
|
+
//
|
|
40
|
+
// The optional process-wide onRetry hook lets the eval harness record retries
|
|
41
|
+
// for its perf/error report.
|
|
42
|
+
const TRANSIENT_DB_ERROR = /ECONNRESET|ENOTCONN|EPIPE|ETIMEDOUT|Connection terminated|terminating connection|server closed the connection|connection to server|read ECONN|EADDRNOTAVAIL|ECONNABORTED|ENOTFOUND|EAI_AGAIN|getaddrinfo/i;
|
|
43
|
+
/** pg connection-class SQLSTATEs: 08xxx connection exceptions, 57P0x
|
|
44
|
+
* admin-shutdown / crash-recovery. All mean "connection went away" → safe to
|
|
45
|
+
* re-run on a fresh connection. */
|
|
46
|
+
const TRANSIENT_PG_CODE = new Set(["08006", "08003", "08000", "08001", "08004", "57P01", "57P02", "57P03"]);
|
|
47
|
+
/** System-catalog unique indexes touched when a label-table creation races
|
|
48
|
+
* itself: pg_class (relname,relnamespace), pg_type (typname,typnamespace),
|
|
49
|
+
* pg_namespace. A 23505 on one of these in the graph layer = the AGE
|
|
50
|
+
* label-creation race. */
|
|
51
|
+
const CATALOG_RACE_CONSTRAINT = /pg_(class|type|namespace)_/i;
|
|
52
|
+
export function isTransientDbError(err) {
|
|
53
|
+
if (err?.code && TRANSIENT_PG_CODE.has(String(err.code)))
|
|
54
|
+
return true;
|
|
55
|
+
const s = `${err?.code ?? ""} ${err?.message ?? ""}`;
|
|
56
|
+
return TRANSIENT_DB_ERROR.test(s);
|
|
57
|
+
}
|
|
58
|
+
/** The AGE lazy-label-creation race, in BOTH manifestations (42P07 duplicate_table
|
|
59
|
+
* and 23505 unique_violation on a pg_catalog index). Re-running the same
|
|
60
|
+
* idempotent graph statement once the label exists succeeds. Label-agnostic. */
|
|
61
|
+
export function isLabelCreationRaceError(err) {
|
|
62
|
+
const code = String(err?.code ?? "");
|
|
63
|
+
if (code === "42P07")
|
|
64
|
+
return true; // duplicate_table
|
|
65
|
+
if (code === "23505") { // unique_violation…
|
|
66
|
+
const constraint = String(err?.constraint ?? "");
|
|
67
|
+
const message = String(err?.message ?? "");
|
|
68
|
+
if (CATALOG_RACE_CONSTRAINT.test(constraint) || CATALOG_RACE_CONSTRAINT.test(message))
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return /already exists/i.test(String(err?.message ?? ""));
|
|
72
|
+
}
|
|
73
|
+
let DB_RETRY_HOOKS = {};
|
|
74
|
+
/** Install process-wide retry observers (used by the eval perf/error report). */
|
|
75
|
+
export function setDbRetryHooks(hooks) { DB_RETRY_HOOKS = hooks ?? {}; }
|
|
76
|
+
export async function withDbRetry(label, fn, opts = {}) {
|
|
77
|
+
const tries = opts.tries ?? 4;
|
|
78
|
+
const isRetryable = opts.isRetryable ?? isTransientDbError;
|
|
79
|
+
let lastErr;
|
|
80
|
+
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
81
|
+
try {
|
|
82
|
+
return await fn();
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
lastErr = err;
|
|
86
|
+
if (!isRetryable(err) || attempt === tries)
|
|
87
|
+
throw err;
|
|
88
|
+
DB_RETRY_HOOKS.onRetry?.({ label, attempt, error: err });
|
|
89
|
+
await new Promise((r) => setTimeout(r, Math.min(200 * 2 ** (attempt - 1), 2000)));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw lastErr;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=db-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-retry.js","sourceRoot":"","sources":["../../src/db-retry.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,8EAA8E;AAC9E,8CAA8C;AAC9C,EAAE;AACF,6DAA6D;AAC7D,6EAA6E;AAC7E,oEAAoE;AACpE,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,yDAAyD;AACzD,oEAAoE;AACpE,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,0EAA0E;AAC1E,EAAE;AACF,+EAA+E;AAC/E,yCAAyC;AACzC,gFAAgF;AAChF,kCAAkC;AAClC,gEAAgE;AAChE,iFAAiF;AACjF,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,6BAA6B;AAE7B,MAAM,kBAAkB,GACpB,2MAA2M,CAAC;AAEhN;;mCAEmC;AACnC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE5G;;;0BAG0B;AAC1B,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;AAE9D,MAAM,UAAU,kBAAkB,CAAC,GAAQ;IACvC,IAAI,GAAG,EAAE,IAAI,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,GAAG,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;IACrD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;gFAEgF;AAChF,MAAM,UAAU,wBAAwB,CAAC,GAAQ;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,CAAmC,kBAAkB;IACvF,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC,CAA8C,oBAAoB;QACrF,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;QAC3C,IAAI,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;IACvG,CAAC;IACD,OAAO,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AAC9D,CAAC;AAMD,IAAI,cAAc,GAAiB,EAAE,CAAC;AACtC,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,KAAmB,IAAU,cAAc,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAQ5F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,KAAa,EAAE,EAAoB,EAAE,OAAoB,EAAE;IAC5F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC3D,IAAI,OAAY,CAAC;IACjB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC;YACD,OAAO,MAAM,EAAE,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,GAAG,GAAG,CAAC;YACd,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK;gBAAE,MAAM,GAAG,CAAC;YACtD,cAAc,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACzD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACtF,CAAC;IACL,CAAC;IACD,MAAM,OAAO,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EmbeddingEndpointConfig } from "./config.js";
|
|
2
|
+
export declare class EmbeddingClient {
|
|
3
|
+
private readonly cfg;
|
|
4
|
+
constructor(cfg: EmbeddingEndpointConfig);
|
|
5
|
+
get dim(): number;
|
|
6
|
+
/** Embed a single string. Returns a vector of length `cfg.dim`. */
|
|
7
|
+
embed(text: string): Promise<number[]>;
|
|
8
|
+
/** Embed a batch. Returns one vector per input, in order. */
|
|
9
|
+
embedBatch(texts: string[]): Promise<number[][]>;
|
|
10
|
+
}
|
|
11
|
+
/** Format a JS number[] as a pgvector literal: "[0.1,0.2,...]". */
|
|
12
|
+
export declare function toVectorLiteral(vec: number[]): string;
|
|
13
|
+
//# sourceMappingURL=embedding-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-client.d.ts","sourceRoot":"","sources":["../../src/embedding-client.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,qBAAa,eAAe;IACZ,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,uBAAuB;IAKzD,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,mEAAmE;IAC7D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAK5C,6DAA6D;IACvD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;CAyCzD;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — Node-side embedding client.
|
|
2
|
+
//
|
|
3
|
+
// Two roles:
|
|
4
|
+
// 1. QUERY-TIME embedding for semantic/hybrid search (the adapter embeds the
|
|
5
|
+
// query string here, then passes the vector to SQL as `$1::vector`).
|
|
6
|
+
// 2. A reference implementation of the request/response contract that the
|
|
7
|
+
// 2. A reference implementation of the request/response contract that any
|
|
8
|
+
// provider's embedding path must speak. The HorizonDB provider mirrors it
|
|
9
|
+
// in-database (sql/006 df.http); the integration test stub server speaks
|
|
10
|
+
// this exact shape.
|
|
11
|
+
//
|
|
12
|
+
// It calls an OpenAI/Azure-OpenAI-compatible embeddings endpoint over HTTP using
|
|
13
|
+
// the global fetch (Node ≥ 18).
|
|
14
|
+
export class EmbeddingClient {
|
|
15
|
+
cfg;
|
|
16
|
+
constructor(cfg) {
|
|
17
|
+
this.cfg = cfg;
|
|
18
|
+
if (!cfg?.url)
|
|
19
|
+
throw new Error("EmbeddingClient requires an endpoint url.");
|
|
20
|
+
if (!cfg.dim || cfg.dim < 1)
|
|
21
|
+
throw new Error("EmbeddingClient requires a positive dim.");
|
|
22
|
+
}
|
|
23
|
+
get dim() {
|
|
24
|
+
return this.cfg.dim;
|
|
25
|
+
}
|
|
26
|
+
/** Embed a single string. Returns a vector of length `cfg.dim`. */
|
|
27
|
+
async embed(text) {
|
|
28
|
+
const [v] = await this.embedBatch([text]);
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
/** Embed a batch. Returns one vector per input, in order. */
|
|
32
|
+
async embedBatch(texts) {
|
|
33
|
+
if (texts.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const inputField = this.cfg.inputField ?? "input";
|
|
36
|
+
const headers = {
|
|
37
|
+
"content-type": "application/json",
|
|
38
|
+
...(this.cfg.headers ?? {}),
|
|
39
|
+
};
|
|
40
|
+
if (this.cfg.apiKey) {
|
|
41
|
+
const headerName = this.cfg.apiKeyHeader ?? "api-key";
|
|
42
|
+
headers[headerName] = this.cfg.bearer ? `Bearer ${this.cfg.apiKey}` : this.cfg.apiKey;
|
|
43
|
+
}
|
|
44
|
+
const body = JSON.stringify({ [inputField]: texts.length === 1 ? texts[0] : texts, model: this.cfg.model });
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), this.cfg.timeoutMs ?? 30_000);
|
|
47
|
+
let res;
|
|
48
|
+
try {
|
|
49
|
+
res = await fetch(this.cfg.url, { method: "POST", headers, body, signal: controller.signal });
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const detail = await res.text().catch(() => "");
|
|
56
|
+
throw new Error(`embedding endpoint ${res.status}: ${detail.slice(0, 200)}`);
|
|
57
|
+
}
|
|
58
|
+
const json = await res.json();
|
|
59
|
+
const data = json?.data;
|
|
60
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
61
|
+
throw new Error("embedding endpoint returned no data[]");
|
|
62
|
+
}
|
|
63
|
+
const out = data.map((d) => {
|
|
64
|
+
const emb = d?.embedding;
|
|
65
|
+
if (!Array.isArray(emb))
|
|
66
|
+
throw new Error("embedding endpoint item missing embedding[]");
|
|
67
|
+
if (emb.length !== this.cfg.dim) {
|
|
68
|
+
throw new Error(`embedding dim mismatch: got ${emb.length}, expected ${this.cfg.dim}`);
|
|
69
|
+
}
|
|
70
|
+
return emb;
|
|
71
|
+
});
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Format a JS number[] as a pgvector literal: "[0.1,0.2,...]". */
|
|
76
|
+
export function toVectorLiteral(vec) {
|
|
77
|
+
return `[${vec.join(",")}]`;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=embedding-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-client.js","sourceRoot":"","sources":["../../src/embedding-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a surface name for SURFACE-FORM dedup only (case, whitespace,
|
|
3
|
+
* punctuation, diacritics). This does NOT solve semantic identity — "Tom Lane"
|
|
4
|
+
* vs "tgl" is resolved by the harvester via searchGraphNodes + mergeGraphNodes,
|
|
5
|
+
* which records an alias. Keep this conservative so we never collapse distinct
|
|
6
|
+
* people.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeName(raw: string): string;
|
|
9
|
+
/** Canonical dedup key: `<normalized-kind>:<normalized-name>`. */
|
|
10
|
+
export declare function nodeKeyOf(kind: string, name: string): string;
|
|
11
|
+
/** Merge alias lists, de-duplicated by surface form, preserving first-seen order. */
|
|
12
|
+
export declare function mergeAliases(existing: string[], incoming: string[]): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a free-text predicate into a grouping key. The original predicate
|
|
15
|
+
* is kept verbatim on the edge; this is only for matching/analytics so the open
|
|
16
|
+
* vocabulary stays queryable without being frozen.
|
|
17
|
+
*/
|
|
18
|
+
export declare function predicateKey(predicate: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Combine an existing edge confidence with a new independent observation.
|
|
21
|
+
* Monotonic, saturating, order-independent: c' = 1 - (1-c_old)(1-c_obs).
|
|
22
|
+
*/
|
|
23
|
+
export declare function reinforceConfidence(existing: number, observation: number): number;
|
|
24
|
+
export declare function clamp01(x: number): number;
|
|
25
|
+
export interface ExistingEdge {
|
|
26
|
+
confidence: number;
|
|
27
|
+
observations: number;
|
|
28
|
+
evidence: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface EdgeUpsertDecision {
|
|
31
|
+
/** create: no edge existed. reinforce: bump observations + noisy-OR.
|
|
32
|
+
* noop: re-assert carried only already-known evidence — idempotent. */
|
|
33
|
+
action: "create" | "reinforce" | "noop";
|
|
34
|
+
confidence: number;
|
|
35
|
+
observations: number;
|
|
36
|
+
evidence: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Evidence is OPTIONAL (the graph stays permissive). Reinforcement counts only
|
|
40
|
+
* NOVEL observations: an assertion reinforces iff it carries ≥1 evidence
|
|
41
|
+
* scopeKey not already on the edge, or carries no evidence at all. Re-asserting
|
|
42
|
+
* with only already-known evidence is an idempotent no-op — a duplicate/replayed
|
|
43
|
+
* harvest of the same fact cannot inflate observations or confidence.
|
|
44
|
+
*/
|
|
45
|
+
export declare function decideEdgeUpsert(input: {
|
|
46
|
+
confidence?: number;
|
|
47
|
+
evidence?: string[];
|
|
48
|
+
}, existing: ExistingEdge | null): EdgeUpsertDecision;
|
|
49
|
+
//# sourceMappingURL=graph-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-model.d.ts","sourceRoot":"","sources":["../../src/graph-model.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASjD;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI5D;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAU7E;AAWD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOtD;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAIjF;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzC;AAID,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IAC/B;2EACuE;IACvE,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,EACnD,QAAQ,EAAE,YAAY,GAAG,IAAI,GAC9B,kBAAkB,CA0BpB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — open-graph quality core (DB-less, unit-tested).
|
|
2
|
+
//
|
|
3
|
+
// An LLM-built, ontology-free graph degenerates into noise without these pure
|
|
4
|
+
// guards. These functions hold the policy; the typed Cypher layer just applies
|
|
5
|
+
// their output. See docs/proposals/enhancedfactstore/01-functional-spec.md §6.
|
|
6
|
+
// ─── Node canonicalization ───────────────────────────────────────────────────
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a surface name for SURFACE-FORM dedup only (case, whitespace,
|
|
9
|
+
* punctuation, diacritics). This does NOT solve semantic identity — "Tom Lane"
|
|
10
|
+
* vs "tgl" is resolved by the harvester via searchGraphNodes + mergeGraphNodes,
|
|
11
|
+
* which records an alias. Keep this conservative so we never collapse distinct
|
|
12
|
+
* people.
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeName(raw) {
|
|
15
|
+
return (raw ?? "")
|
|
16
|
+
.normalize("NFKD")
|
|
17
|
+
.replace(/(\p{Script=Latin})\p{M}+/gu, "$1") // strip Latin diacritics
|
|
18
|
+
.normalize("NFC")
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ") // punctuation → space, keep all scripts
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/\s+/g, " ");
|
|
23
|
+
}
|
|
24
|
+
/** Canonical dedup key: `<normalized-kind>:<normalized-name>`. */
|
|
25
|
+
export function nodeKeyOf(kind, name) {
|
|
26
|
+
const k = normalizeName(kind).replace(/\s+/g, "_");
|
|
27
|
+
const n = normalizeName(name).replace(/\s+/g, "-");
|
|
28
|
+
return `${k}:${n}`;
|
|
29
|
+
}
|
|
30
|
+
/** Merge alias lists, de-duplicated by surface form, preserving first-seen order. */
|
|
31
|
+
export function mergeAliases(existing, incoming) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const a of [...(existing ?? []), ...(incoming ?? [])]) {
|
|
35
|
+
const norm = normalizeName(a);
|
|
36
|
+
if (!norm || seen.has(norm))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(norm);
|
|
39
|
+
out.push(a.trim());
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
// ─── Predicate normalization ─────────────────────────────────────────────────
|
|
44
|
+
// Tiny stopword set so "revives argument from" and "revives the argument from"
|
|
45
|
+
// group together. Deliberately small — we group, we do NOT enforce a vocabulary.
|
|
46
|
+
const PREDICATE_STOPWORDS = new Set([
|
|
47
|
+
"a", "an", "the", "to", "of", "in", "on", "with", "for", "from", "by",
|
|
48
|
+
"that", "this", "it", "its", "their", "his", "her",
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a free-text predicate into a grouping key. The original predicate
|
|
52
|
+
* is kept verbatim on the edge; this is only for matching/analytics so the open
|
|
53
|
+
* vocabulary stays queryable without being frozen.
|
|
54
|
+
*/
|
|
55
|
+
export function predicateKey(predicate) {
|
|
56
|
+
const words = normalizeName(predicate)
|
|
57
|
+
.split(" ")
|
|
58
|
+
.filter((w) => w && !PREDICATE_STOPWORDS.has(w));
|
|
59
|
+
// crude stem: drop a trailing 's' on each token (comments→comment)
|
|
60
|
+
const stemmed = words.map((w) => (w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w));
|
|
61
|
+
return stemmed.join("_");
|
|
62
|
+
}
|
|
63
|
+
// ─── Confidence reinforcement (noisy-OR) ─────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Combine an existing edge confidence with a new independent observation.
|
|
66
|
+
* Monotonic, saturating, order-independent: c' = 1 - (1-c_old)(1-c_obs).
|
|
67
|
+
*/
|
|
68
|
+
export function reinforceConfidence(existing, observation) {
|
|
69
|
+
const a = clamp01(existing);
|
|
70
|
+
const b = clamp01(observation);
|
|
71
|
+
return 1 - (1 - a) * (1 - b);
|
|
72
|
+
}
|
|
73
|
+
export function clamp01(x) {
|
|
74
|
+
if (!Number.isFinite(x))
|
|
75
|
+
return 0;
|
|
76
|
+
if (x < 0)
|
|
77
|
+
return 0;
|
|
78
|
+
if (x > 1)
|
|
79
|
+
return 1;
|
|
80
|
+
return x;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Evidence is OPTIONAL (the graph stays permissive). Reinforcement counts only
|
|
84
|
+
* NOVEL observations: an assertion reinforces iff it carries ≥1 evidence
|
|
85
|
+
* scopeKey not already on the edge, or carries no evidence at all. Re-asserting
|
|
86
|
+
* with only already-known evidence is an idempotent no-op — a duplicate/replayed
|
|
87
|
+
* harvest of the same fact cannot inflate observations or confidence.
|
|
88
|
+
*/
|
|
89
|
+
export function decideEdgeUpsert(input, existing) {
|
|
90
|
+
const obsConfidence = clamp01(input.confidence ?? 1.0);
|
|
91
|
+
const incoming = [...new Set(input.evidence ?? [])];
|
|
92
|
+
if (!existing) {
|
|
93
|
+
return { action: "create", confidence: obsConfidence, observations: 1, evidence: incoming };
|
|
94
|
+
}
|
|
95
|
+
const known = new Set(existing.evidence ?? []);
|
|
96
|
+
const novel = incoming.filter((e) => !known.has(e));
|
|
97
|
+
if (incoming.length > 0 && novel.length === 0) {
|
|
98
|
+
return {
|
|
99
|
+
action: "noop",
|
|
100
|
+
confidence: existing.confidence,
|
|
101
|
+
observations: existing.observations,
|
|
102
|
+
evidence: existing.evidence,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
action: "reinforce",
|
|
107
|
+
confidence: reinforceConfidence(existing.confidence, obsConfidence),
|
|
108
|
+
observations: existing.observations + 1,
|
|
109
|
+
evidence: [...(existing.evidence ?? []), ...novel],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=graph-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-model.js","sourceRoot":"","sources":["../../src/graph-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AccessContext, GraphEdgeHit, GraphEdgeInput, GraphEdgeQuery, GraphNodeHit, GraphNodeInput, GraphNodeQuery, GraphNodeRef, GraphEdgeRef, GraphEvidenceRemovalResult, SubGraph } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load the AGE shared library for this session. On managed Postgres where age
|
|
4
|
+
* is preloaded via shared_preload_libraries, an explicit LOAD is rejected with
|
|
5
|
+
* `access to library "age" is not allowed` — tolerated, since preloaded means
|
|
6
|
+
* there is nothing to do.
|
|
7
|
+
*/
|
|
8
|
+
export declare function prepareAgeSession(client: any): Promise<void>;
|
|
9
|
+
/** Parse a scalar agtype column value into a JS value. */
|
|
10
|
+
export declare function ag(v: any): any;
|
|
11
|
+
/** Parse an agtype array column into string[]. */
|
|
12
|
+
export declare function agArr(v: any): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Typed Cypher layer (AGE). Provides the GraphStore READ/WRITE method bodies;
|
|
15
|
+
* `HorizonDBGraphStore` wraps this with the lifecycle (initialize/close) to form
|
|
16
|
+
* the full `GraphStore` provider (07 D2). This inner class deliberately does NOT
|
|
17
|
+
* implement the lifecycle, so it is not itself a `GraphStore`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class GraphQueries {
|
|
20
|
+
private readonly pool;
|
|
21
|
+
private readonly graphName;
|
|
22
|
+
private readonly prepared;
|
|
23
|
+
constructor(pool: any, graphName: string);
|
|
24
|
+
private withAge;
|
|
25
|
+
private cypher;
|
|
26
|
+
searchGraphNodes(q: GraphNodeQuery, access?: AccessContext): Promise<GraphNodeHit[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Idempotent, duplicate-anchor-proof evidence linking. A naive
|
|
29
|
+
* `MERGE (f:Fact {scope_key}) MERGE (e)-[:EVIDENCED_BY]->(f)` is NOT safe
|
|
30
|
+
* here: AGE has no unique constraints, so concurrent upserts can race the
|
|
31
|
+
* anchor MERGE into duplicate :Fact nodes — after which a later MERGE
|
|
32
|
+
* binds EVERY duplicate and mints extra edges. Instead: skip when any
|
|
33
|
+
* edge already exists; bind exactly ONE anchor (WITH … LIMIT 1) or create
|
|
34
|
+
* it when none exists.
|
|
35
|
+
*/
|
|
36
|
+
private linkEvidenceAnchor;
|
|
37
|
+
/** EVIDENCED_BY scopeKeys per node, ACL-filtered (01 §6.1a), deduped
|
|
38
|
+
* (duplicate anchors must never surface as duplicate evidence keys). */
|
|
39
|
+
private evidenceFor;
|
|
40
|
+
searchGraphEdges(q: GraphEdgeQuery, access?: AccessContext): Promise<GraphEdgeHit[]>;
|
|
41
|
+
graphNeighbourhood(nodeKey: string, depth: number, _access?: AccessContext, opts?: {
|
|
42
|
+
namespace?: string;
|
|
43
|
+
}): Promise<SubGraph>;
|
|
44
|
+
/**
|
|
45
|
+
* Cheap whole-graph counts for `graph_stats` (enhancedfactstore 07 P5):
|
|
46
|
+
* a single `count()` Cypher per axis instead of a client-side fan-out.
|
|
47
|
+
* AGE has no edge-only catalog, so edges are counted via a directed match.
|
|
48
|
+
*/
|
|
49
|
+
graphStats(opts?: {
|
|
50
|
+
namespace?: string;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
nodeCount: number;
|
|
53
|
+
edgeCount: number;
|
|
54
|
+
}>;
|
|
55
|
+
upsertGraphNode(n: GraphNodeInput): Promise<GraphNodeRef>;
|
|
56
|
+
upsertGraphEdge(e: GraphEdgeInput): Promise<GraphEdgeRef>;
|
|
57
|
+
mergeGraphNodes(fromKey: string, intoKey: string, reason: string, opts?: {
|
|
58
|
+
namespace?: string;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
deleteGraphNode(nodeKey: string, opts?: {
|
|
61
|
+
namespace?: string;
|
|
62
|
+
}): Promise<boolean>;
|
|
63
|
+
deleteGraphEdge(fromKey: string, toKey: string, predicateKey: string, opts?: {
|
|
64
|
+
namespace?: string;
|
|
65
|
+
}): Promise<boolean>;
|
|
66
|
+
removeGraphEvidence(scopeKey: string, opts?: {
|
|
67
|
+
namespace?: string;
|
|
68
|
+
}): Promise<GraphEvidenceRemovalResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Drop graph data for an EXACT namespace (graph-fact-search enhancements,
|
|
71
|
+
* `deleteGraphNamespace`). NOT subtree: only records whose own namespace
|
|
72
|
+
* equals `namespace` are removed — child namespaces are deleted explicitly.
|
|
73
|
+
* Edges with this namespace are removed first; then nodes with this
|
|
74
|
+
* namespace are DETACH DELETEd (which also clears their remaining edges and
|
|
75
|
+
* EVIDENCED_BY anchors). Idempotent / re-runnable. Refuses the reserved
|
|
76
|
+
* `default` token / empty (the NULL partition is never bulk-deleted).
|
|
77
|
+
*/
|
|
78
|
+
deleteNamespaceData(namespace: string): Promise<{
|
|
79
|
+
nodesDeleted: number;
|
|
80
|
+
edgesDeleted: number;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=graph-queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-queries.d.ts","sourceRoot":"","sources":["../../src/graph-queries.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACR,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAC3D,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EAAE,QAAQ,EACjH,MAAM,YAAY,CAAC;AA+EpB;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AAED,0DAA0D;AAC1D,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,GAAG,CAK9B;AAED,kDAAkD;AAClD,wBAAgB,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAGtC;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IAWT,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAO,OAAO,CAAC,QAAQ,CAAC,SAAS;IAFlE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;gBAErB,IAAI,EAAE,GAAG,EAAmB,SAAS,EAAE,MAAM;YAE5D,OAAO;YAkBP,MAAM;IAiCd,gBAAgB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAqF1F;;;;;;;;OAQG;YACW,kBAAkB;IAkBhC;4EACwE;YAC1D,WAAW;IAkBnB,gBAAgB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA2BpF,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4CvI;;;;OAIG;IACG,UAAU,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAiBhG,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAyCzD,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAiEzD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAuFnH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAYrF,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1H,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,0BAA0B,CAAC;IA6EnH;;;;;;;;OAQG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CA+BxG"}
|