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,21 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — public exports.
|
|
2
|
+
//
|
|
3
|
+
// HorizonDB-backed providers implementing the @pilotswarm/sdk fact/graph
|
|
4
|
+
// contracts. The DB-less core (query-builder, graph-model) and the re-exported
|
|
5
|
+
// type contracts are the stable, unit-tested surface; HorizonDBFactStore (facts
|
|
6
|
+
// + search + embedder) and HorizonDBGraphStore (separate AGE-only graph provider,
|
|
7
|
+
// 07 D2) are the live providers validated by the integration suite against a real
|
|
8
|
+
// HorizonDB.
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./query-builder.js";
|
|
11
|
+
export * from "./graph-model.js";
|
|
12
|
+
export * from "./config.js";
|
|
13
|
+
export * from "./embedding-client.js";
|
|
14
|
+
export * from "./horizon-store.js";
|
|
15
|
+
export * from "./graph-store.js";
|
|
16
|
+
export * from "./agent-tools.js";
|
|
17
|
+
export { isTransientDbError, withDbRetry, setDbRetryHooks } from "./db-retry.js";
|
|
18
|
+
export { GraphQueries, prepareAgeSession } from "./graph-queries.js";
|
|
19
|
+
export { loadMigrations, runMigrations, migrationsDir, HORIZON_FACTS_LOCK_SEED, GRAPH_OWNED_MIGRATIONS, isGraphOwnedMigration } from "./horizon-migrator.js";
|
|
20
|
+
export { assertExtensionsAvailable, assertFactExtensions, assertGraphExtensions, assertDurableHttpUsable, missingExtensions, missingFactExtensions, missingGraphExtensions, } from "./preconditions.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,gFAAgF;AAChF,kFAAkF;AAClF,kFAAkF;AAClF,aAAa;AAEb,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7J,OAAO,EACH,yBAAyB,EAAE,oBAAoB,EAAE,qBAAqB,EACtE,uBAAuB,EACvB,iBAAiB,EAAE,qBAAqB,EAAE,sBAAsB,GACnE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Pieces missing BEFORE migrations run — full bundled surface (back-compat). */
|
|
2
|
+
export declare function missingExtensions(pool: any): Promise<string[]>;
|
|
3
|
+
/** Fact-provider missing extensions (vector / pg_textsearch / pg_durable). */
|
|
4
|
+
export declare function missingFactExtensions(pool: any): Promise<string[]>;
|
|
5
|
+
/** Graph-provider missing extensions (age). */
|
|
6
|
+
export declare function missingGraphExtensions(pool: any): Promise<string[]>;
|
|
7
|
+
/** Throw the itemized fail-fast error if any required extension is unavailable
|
|
8
|
+
* (full bundled surface — back-compat). */
|
|
9
|
+
export declare function assertExtensionsAvailable(pool: any): Promise<void>;
|
|
10
|
+
/** Fail-fast for the enhanced FACT provider (vector / pg_textsearch / pg_durable). */
|
|
11
|
+
export declare function assertFactExtensions(pool: any): Promise<void>;
|
|
12
|
+
/** Fail-fast for the GRAPH provider (age only). */
|
|
13
|
+
export declare function assertGraphExtensions(pool: any): Promise<void>;
|
|
14
|
+
/** Post-migration checks: df.http present and usable by this role. */
|
|
15
|
+
export declare function assertDurableHttpUsable(pool: any): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=preconditions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preconditions.d.ts","sourceRoot":"","sources":["../../src/preconditions.ts"],"names":[],"mappings":"AAmEA,iFAAiF;AACjF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAEpE;AAED,8EAA8E;AAC9E,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAExE;AAED,+CAA+C;AAC/C,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAEzE;AAED;4CAC4C;AAC5C,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAExE;AAED,sFAAsF;AACtF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnE;AAED,mDAAmD;AACnD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED,sEAAsE;AACtE,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — fail-fast preconditions (01-functional-spec §5.5).
|
|
2
|
+
//
|
|
3
|
+
// `initialize()` throws ONE precise, ITEMIZED error naming every missing piece
|
|
4
|
+
// and its fix. No feature flags, no silent fallbacks: lexical is BM25 via
|
|
5
|
+
// pg_textsearch (ts_rank is not a substitute), semantic needs pgvector, the
|
|
6
|
+
// graph needs AGE, and the embedder needs pg_durable with df.http granted.
|
|
7
|
+
import { withDbRetry } from "./db-retry.js";
|
|
8
|
+
/** Extensions the enhanced FACT provider (HorizonDBFactStore) needs (07 D2):
|
|
9
|
+
* pgvector + pg_textsearch + pg_durable. NOT age — graph is a separate provider. */
|
|
10
|
+
const FACT_REQUIRED = [
|
|
11
|
+
{
|
|
12
|
+
name: "vector",
|
|
13
|
+
why: "semantic kNN over facts.embedding (pgvector)",
|
|
14
|
+
fix: "install pgvector / add 'vector' to the cluster's allow-listed extensions",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "pg_textsearch",
|
|
18
|
+
why: "BM25 lexical ranking (ts_rank is not an acceptable fallback)",
|
|
19
|
+
fix: "add 'pg_textsearch' to the cluster's allow-listed extensions",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "pg_durable",
|
|
23
|
+
why: "the durable in-database embedding loop (df.loop/df.http)",
|
|
24
|
+
fix: "add 'pg_durable' to shared_preload_libraries and the extension allow-list",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
/** Extensions the GRAPH provider (HorizonDBGraphStore) needs (07 D2): AGE only,
|
|
28
|
+
* so it can pair with a plain PgFactStore (base-facts + graph tier). */
|
|
29
|
+
const GRAPH_REQUIRED = [
|
|
30
|
+
{
|
|
31
|
+
name: "age",
|
|
32
|
+
why: "the open knowledge graph (Apache AGE)",
|
|
33
|
+
fix: "install Apache AGE / add 'age' to shared_preload_libraries and the extension allow-list",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
/** Union — the full bundled HorizonDB surface (back-compat). */
|
|
37
|
+
const REQUIRED = [...FACT_REQUIRED, ...GRAPH_REQUIRED];
|
|
38
|
+
async function missingFrom(pool, required) {
|
|
39
|
+
const { rows } = await withDbRetry("horizon_preconditions_extensions", () => pool.query(`SELECT name FROM pg_available_extensions WHERE name = ANY($1)`, [required.map((r) => r.name)]), { tries: 6 });
|
|
40
|
+
const available = new Set(rows.map((r) => r.name));
|
|
41
|
+
return required.filter((r) => !available.has(r.name)).map((r) => r.name);
|
|
42
|
+
}
|
|
43
|
+
function assertFrom(missing, required, surface) {
|
|
44
|
+
if (missing.length === 0)
|
|
45
|
+
return;
|
|
46
|
+
const lines = missing.map((name) => {
|
|
47
|
+
const r = required.find((x) => x.name === name);
|
|
48
|
+
return ` - ${name}: ${r.why}. Fix: ${r.fix}`;
|
|
49
|
+
});
|
|
50
|
+
throw new Error(`${surface} preconditions failed — this database is missing ${missing.length} required ` +
|
|
51
|
+
`extension(s):\n${lines.join("\n")}\n` +
|
|
52
|
+
`${surface} requires HorizonDB (or equivalent). There are no fallbacks; ` +
|
|
53
|
+
`point the connection string at a capable cluster.`);
|
|
54
|
+
}
|
|
55
|
+
/** Pieces missing BEFORE migrations run — full bundled surface (back-compat). */
|
|
56
|
+
export async function missingExtensions(pool) {
|
|
57
|
+
return missingFrom(pool, REQUIRED);
|
|
58
|
+
}
|
|
59
|
+
/** Fact-provider missing extensions (vector / pg_textsearch / pg_durable). */
|
|
60
|
+
export async function missingFactExtensions(pool) {
|
|
61
|
+
return missingFrom(pool, FACT_REQUIRED);
|
|
62
|
+
}
|
|
63
|
+
/** Graph-provider missing extensions (age). */
|
|
64
|
+
export async function missingGraphExtensions(pool) {
|
|
65
|
+
return missingFrom(pool, GRAPH_REQUIRED);
|
|
66
|
+
}
|
|
67
|
+
/** Throw the itemized fail-fast error if any required extension is unavailable
|
|
68
|
+
* (full bundled surface — back-compat). */
|
|
69
|
+
export async function assertExtensionsAvailable(pool) {
|
|
70
|
+
assertFrom(await missingExtensions(pool), REQUIRED, "EnhancedFactStore");
|
|
71
|
+
}
|
|
72
|
+
/** Fail-fast for the enhanced FACT provider (vector / pg_textsearch / pg_durable). */
|
|
73
|
+
export async function assertFactExtensions(pool) {
|
|
74
|
+
assertFrom(await missingFactExtensions(pool), FACT_REQUIRED, "HorizonDBFactStore");
|
|
75
|
+
}
|
|
76
|
+
/** Fail-fast for the GRAPH provider (age only). */
|
|
77
|
+
export async function assertGraphExtensions(pool) {
|
|
78
|
+
assertFrom(await missingGraphExtensions(pool), GRAPH_REQUIRED, "HorizonDBGraphStore");
|
|
79
|
+
}
|
|
80
|
+
/** Post-migration checks: df.http present and usable by this role. */
|
|
81
|
+
export async function assertDurableHttpUsable(pool) {
|
|
82
|
+
const { rows } = await withDbRetry("horizon_preconditions_df_http", () => pool.query(`
|
|
83
|
+
SELECT EXISTS (
|
|
84
|
+
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
85
|
+
WHERE n.nspname = 'df' AND p.proname = 'http'
|
|
86
|
+
) AS has_http`), { tries: 6 });
|
|
87
|
+
if (!rows[0]?.has_http) {
|
|
88
|
+
throw new Error("EnhancedFactStore preconditions failed — pg_durable is installed but df.http() is absent. " +
|
|
89
|
+
"Fix: ensure pg_durable is in shared_preload_libraries (restart required) and re-run CREATE EXTENSION pg_durable.");
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await withDbRetry("horizon_preconditions_df_usage", () => pool.query(`SELECT df.getvar('__hz_precondition_probe')`), { tries: 6 });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new Error("EnhancedFactStore preconditions failed — this role cannot use the df schema. " +
|
|
96
|
+
`Fix: have an admin run SELECT df.grant_usage('<role>', true, false). Underlying error: ${err?.message ?? err}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=preconditions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preconditions.js","sourceRoot":"","sources":["../../src/preconditions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SearchWeights } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize the raw lexical query (BM25). The scoring function treats text as
|
|
4
|
+
* data, so we only normalize whitespace. Returns null for empty queries —
|
|
5
|
+
* callers turn that into a defined empty result, not a crash (04 L4).
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildLexicalQuery(raw: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a namespace ("skills") into a SQL LIKE prefix ("skills/%").
|
|
10
|
+
* Mirrors PilotSwarm's reserved knowledge namespaces.
|
|
11
|
+
*/
|
|
12
|
+
export declare function namespacePrefix(namespace?: string): string | null;
|
|
13
|
+
export interface Candidate {
|
|
14
|
+
scopeKey: string;
|
|
15
|
+
/** Raw per-signal scores. Any subset may be present. */
|
|
16
|
+
lexical?: number;
|
|
17
|
+
semantic?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface FusedCandidate {
|
|
20
|
+
scopeKey: string;
|
|
21
|
+
score: number;
|
|
22
|
+
signals: {
|
|
23
|
+
lexical?: number;
|
|
24
|
+
semantic?: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Min-max normalize a list of numbers into 0..1. Constant lists map to 1. */
|
|
28
|
+
export declare function normalize(values: number[]): number[];
|
|
29
|
+
/**
|
|
30
|
+
* Weighted-normalized-score fusion.
|
|
31
|
+
*
|
|
32
|
+
* Each signal is min-max normalized across its own candidate set, then combined
|
|
33
|
+
* with the configured weights. A candidate missing a signal contributes 0 for
|
|
34
|
+
* that signal. Returns candidates sorted by fused score descending.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fuseWeighted(candidates: Candidate[], weights?: SearchWeights): FusedCandidate[];
|
|
37
|
+
//# sourceMappingURL=query-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../src/query-builder.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AAID,MAAM,WAAW,SAAS;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD;AAID,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAUpD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,cAAc,EAAE,CAqB/F"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — DB-less core.
|
|
2
|
+
//
|
|
3
|
+
// This module holds the parts of the read path that do NOT need a database:
|
|
4
|
+
// - query/namespace fragment normalization (unit-testable)
|
|
5
|
+
// - candidate fusion (combining lexical/semantic candidates into a single
|
|
6
|
+
// ranked list). There is NO graph signal: searchFacts is facts-store-only
|
|
7
|
+
// (01-functional-spec §4.2); graph retrieval is the separate GraphStore.
|
|
8
|
+
//
|
|
9
|
+
// Keeping these pure makes the fusion algorithm A/B-testable offline and keeps
|
|
10
|
+
// the HorizonDB adapter thin.
|
|
11
|
+
// ─── query/namespace normalization ──────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Normalize the raw lexical query (BM25). The scoring function treats text as
|
|
14
|
+
* data, so we only normalize whitespace. Returns null for empty queries —
|
|
15
|
+
* callers turn that into a defined empty result, not a crash (04 L4).
|
|
16
|
+
*/
|
|
17
|
+
export function buildLexicalQuery(raw) {
|
|
18
|
+
const trimmed = (raw ?? "").replace(/\s+/g, " ").trim();
|
|
19
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a namespace ("skills") into a SQL LIKE prefix ("skills/%").
|
|
23
|
+
* Mirrors PilotSwarm's reserved knowledge namespaces.
|
|
24
|
+
*/
|
|
25
|
+
export function namespacePrefix(namespace) {
|
|
26
|
+
if (!namespace)
|
|
27
|
+
return null;
|
|
28
|
+
const clean = namespace.replace(/\/+$/, "");
|
|
29
|
+
return clean.length > 0 ? `${clean}/%` : null;
|
|
30
|
+
}
|
|
31
|
+
const DEFAULT_WEIGHTS = { lexical: 1, semantic: 1 };
|
|
32
|
+
/** Min-max normalize a list of numbers into 0..1. Constant lists map to 1. */
|
|
33
|
+
export function normalize(values) {
|
|
34
|
+
if (values.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
let min = Infinity;
|
|
37
|
+
let max = -Infinity;
|
|
38
|
+
for (const v of values) {
|
|
39
|
+
if (v < min)
|
|
40
|
+
min = v;
|
|
41
|
+
if (v > max)
|
|
42
|
+
max = v;
|
|
43
|
+
}
|
|
44
|
+
if (max === min)
|
|
45
|
+
return values.map(() => (max === 0 ? 0 : 1));
|
|
46
|
+
return values.map((v) => (v - min) / (max - min));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Weighted-normalized-score fusion.
|
|
50
|
+
*
|
|
51
|
+
* Each signal is min-max normalized across its own candidate set, then combined
|
|
52
|
+
* with the configured weights. A candidate missing a signal contributes 0 for
|
|
53
|
+
* that signal. Returns candidates sorted by fused score descending.
|
|
54
|
+
*/
|
|
55
|
+
export function fuseWeighted(candidates, weights) {
|
|
56
|
+
const w = { ...DEFAULT_WEIGHTS, ...(weights ?? {}) };
|
|
57
|
+
const lexNorm = normByKey(candidates, "lexical");
|
|
58
|
+
const semNorm = normByKey(candidates, "semantic");
|
|
59
|
+
const fused = candidates.map((c) => {
|
|
60
|
+
const signals = {};
|
|
61
|
+
let score = 0;
|
|
62
|
+
if (c.lexical !== undefined) {
|
|
63
|
+
signals.lexical = c.lexical;
|
|
64
|
+
score += w.lexical * (lexNorm.get(c.scopeKey) ?? 0);
|
|
65
|
+
}
|
|
66
|
+
if (c.semantic !== undefined) {
|
|
67
|
+
signals.semantic = c.semantic;
|
|
68
|
+
score += w.semantic * (semNorm.get(c.scopeKey) ?? 0);
|
|
69
|
+
}
|
|
70
|
+
return { scopeKey: c.scopeKey, score, signals };
|
|
71
|
+
});
|
|
72
|
+
return fused.sort((a, b) => b.score - a.score || a.scopeKey.localeCompare(b.scopeKey));
|
|
73
|
+
}
|
|
74
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
75
|
+
function normByKey(candidates, signal) {
|
|
76
|
+
const present = candidates.filter((c) => typeof c[signal] === "number");
|
|
77
|
+
const norm = normalize(present.map((c) => c[signal]));
|
|
78
|
+
const out = new Map();
|
|
79
|
+
present.forEach((c, i) => out.set(c.scopeKey, norm[i]));
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=query-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../../src/query-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Quote a SQL identifier (schema/table/graph). Rejects anything non-trivial. */
|
|
2
|
+
export declare function ident(name: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Escape a JS string as a single-quoted AGE Cypher string literal.
|
|
5
|
+
*
|
|
6
|
+
* These literals are consumed by AGE's Cypher (openCypher) parser inside
|
|
7
|
+
* `cypher(graph, $$ ... $$)`, NOT by the SQL parser — so a single quote is
|
|
8
|
+
* escaped with a backslash (`\'`), the Cypher way, NOT by SQL-style doubling
|
|
9
|
+
* (`''`). Doubling produces `'Andres''s VM'`, which the Cypher parser reads as
|
|
10
|
+
* the string `'Andres'` followed by stray tokens → `syntax error at or near
|
|
11
|
+
* "'s VM'"` (verified against live AGE), silently dropping the write. Backslash
|
|
12
|
+
* is escaped first so the quote-escape we add is not itself re-escaped; NULs are
|
|
13
|
+
* stripped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cypherStr(value: string): string;
|
|
16
|
+
/** Render a number safely as a Cypher numeric literal. */
|
|
17
|
+
export declare function cypherNum(n: number): string;
|
|
18
|
+
/** Render a string[] as a Cypher list literal of escaped strings. */
|
|
19
|
+
export declare function cypherStrList(values: string[]): string;
|
|
20
|
+
//# sourceMappingURL=sql-util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-util.d.ts","sourceRoot":"","sources":["../../src/sql-util.ts"],"names":[],"mappings":"AAOA,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — small SQL helpers for the adapter.
|
|
2
|
+
//
|
|
3
|
+
// AGE Cypher cannot be fully parameterized through pg bind params (the cypher()
|
|
4
|
+
// function takes a literal query string), so the adapter must build Cypher with
|
|
5
|
+
// embedded literals. These helpers make that safe: every value is escaped, and
|
|
6
|
+
// identifiers are validated against a strict allowlist.
|
|
7
|
+
/** Quote a SQL identifier (schema/table/graph). Rejects anything non-trivial. */
|
|
8
|
+
export function ident(name) {
|
|
9
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
10
|
+
throw new Error(`unsafe SQL identifier: ${JSON.stringify(name)}`);
|
|
11
|
+
}
|
|
12
|
+
return `"${name}"`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Escape a JS string as a single-quoted AGE Cypher string literal.
|
|
16
|
+
*
|
|
17
|
+
* These literals are consumed by AGE's Cypher (openCypher) parser inside
|
|
18
|
+
* `cypher(graph, $$ ... $$)`, NOT by the SQL parser — so a single quote is
|
|
19
|
+
* escaped with a backslash (`\'`), the Cypher way, NOT by SQL-style doubling
|
|
20
|
+
* (`''`). Doubling produces `'Andres''s VM'`, which the Cypher parser reads as
|
|
21
|
+
* the string `'Andres'` followed by stray tokens → `syntax error at or near
|
|
22
|
+
* "'s VM'"` (verified against live AGE), silently dropping the write. Backslash
|
|
23
|
+
* is escaped first so the quote-escape we add is not itself re-escaped; NULs are
|
|
24
|
+
* stripped.
|
|
25
|
+
*/
|
|
26
|
+
export function cypherStr(value) {
|
|
27
|
+
const cleaned = String(value).replace(/\u0000/g, "");
|
|
28
|
+
return `'${cleaned.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
29
|
+
}
|
|
30
|
+
/** Render a number safely as a Cypher numeric literal. */
|
|
31
|
+
export function cypherNum(n) {
|
|
32
|
+
if (!Number.isFinite(n))
|
|
33
|
+
throw new Error(`non-finite number in cypher: ${n}`);
|
|
34
|
+
return String(n);
|
|
35
|
+
}
|
|
36
|
+
/** Render a string[] as a Cypher list literal of escaped strings. */
|
|
37
|
+
export function cypherStrList(values) {
|
|
38
|
+
return `[${values.map(cypherStr).join(", ")}]`;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=sql-util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-util.js","sourceRoot":"","sources":["../../src/sql-util.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,wDAAwD;AAExD,iFAAiF;AACjF,MAAM,UAAU,KAAK,CAAC,IAAY;IAC9B,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,IAAI,IAAI,GAAG,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AACtE,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,CAAS;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;IAC9E,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,MAAgB;IAC1C,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { FactRecord, StoreFactInput, StoredFactResult, ReadFactsQuery, DeleteFactInput, DeletedFactResult, DeletedFactsResult, FactsNamespace, FactsStatsRow, FactsTombstoneStats, ForcePurgeFactsInput, AccessContext, FactStore, CrawledFactStamp, EnhancedFactStore, FactsCapabilities, SearchMode, SearchWeights, SearchOpts, SimilarOpts, ScoredFact, SearchResult, EmbedderStatus, EmbedderLoopStatus, EmbeddingEndpointConfig, GraphStore, GraphNodeInput, GraphEdgeInput, GraphNamespaceQuery, GraphNodeQuery, GraphEdgeQuery, GraphNodeRef, GraphNodeHit, GraphEdgeRef, GraphEdgeHit, GraphEvidenceRemovalResult, SubGraph, GraphNamespaceFrontmatter, GraphNamespaceInfo, GraphNamespaceInput, GraphNamespaceListQuery, GraphNamespaceDeleteResult, } from "pilotswarm-sdk";
|
|
2
|
+
import type { AccessContext } from "pilotswarm-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a fact scope_key is readable under the given access context, decided
|
|
5
|
+
* purely from the key's shape: `shared:` always passes; `session:<id>:` passes
|
|
6
|
+
* iff `<id>` is the reader's or a granted session; unrestricted passes all.
|
|
7
|
+
* Malformed `session:` keys fail closed. (Mirror of the SDK helper; kept local
|
|
8
|
+
* to avoid a runtime dependency on the SDK barrel.)
|
|
9
|
+
*/
|
|
10
|
+
export declare function scopeKeyAccessible(scopeKey: string, access?: AccessContext): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated The graph is its own interface `GraphStore` (07 D2). Alias kept
|
|
13
|
+
* for the provider's internal back-compat; prefer `GraphStore`.
|
|
14
|
+
*/
|
|
15
|
+
export type { GraphStore as GraphInterface } from "pilotswarm-sdk";
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAUA,YAAY,EAER,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,SAAS,EACT,gBAAgB,EAEhB,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EAEvB,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,0BAA0B,EAC1B,QAAQ,EAER,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC7B,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAYpF;AAED;;;GAGG;AACH,YAAY,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// pilotswarm-horizon-store — type contracts.
|
|
2
|
+
//
|
|
3
|
+
// These contracts are OWNED BY THE SDK (`pilotswarm-sdk`, enhancedfactstore 07
|
|
4
|
+
// P1). This module re-exports them so the provider's own files keep importing
|
|
5
|
+
// from a single local `./types.js` while the DEFINITIONS live in the SDK. The
|
|
6
|
+
// provider IMPLEMENTS these contracts; it does not define them.
|
|
7
|
+
//
|
|
8
|
+
// Type-only re-exports (no runtime cost; no import cycle — the SDK does not
|
|
9
|
+
// depend on this package).
|
|
10
|
+
/**
|
|
11
|
+
* Whether a fact scope_key is readable under the given access context, decided
|
|
12
|
+
* purely from the key's shape: `shared:` always passes; `session:<id>:` passes
|
|
13
|
+
* iff `<id>` is the reader's or a granted session; unrestricted passes all.
|
|
14
|
+
* Malformed `session:` keys fail closed. (Mirror of the SDK helper; kept local
|
|
15
|
+
* to avoid a runtime dependency on the SDK barrel.)
|
|
16
|
+
*/
|
|
17
|
+
export function scopeKeyAccessible(scopeKey, access) {
|
|
18
|
+
if (access?.unrestricted)
|
|
19
|
+
return true;
|
|
20
|
+
if (scopeKey.startsWith("shared:"))
|
|
21
|
+
return true;
|
|
22
|
+
if (scopeKey.startsWith("session:")) {
|
|
23
|
+
const rest = scopeKey.slice("session:".length);
|
|
24
|
+
const sep = rest.indexOf(":");
|
|
25
|
+
if (sep <= 0)
|
|
26
|
+
return false;
|
|
27
|
+
const sessionId = rest.slice(0, sep);
|
|
28
|
+
if (access?.readerSessionId && sessionId === access.readerSessionId)
|
|
29
|
+
return true;
|
|
30
|
+
if (access?.grantedSessionIds?.includes(sessionId))
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,2BAA2B;AA0D3B;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,MAAsB;IACvE,IAAI,MAAM,EAAE,YAAY;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrC,IAAI,MAAM,EAAE,eAAe,IAAI,SAAS,KAAK,MAAM,CAAC,eAAe;YAAE,OAAO,IAAI,CAAC;QACjF,IAAI,MAAM,EAAE,iBAAiB,EAAE,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;IACpE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- 0001_facts_table — base facts table (PilotSwarm parity) + enrichment columns
|
|
2
|
+
-- + the write-resets-pending-state trigger (03-design §2.1).
|
|
3
|
+
--
|
|
4
|
+
-- Tokens substituted by horizon-migrator: {{SCHEMA}}, {{EMBEDDING_DIM}}.
|
|
5
|
+
|
|
6
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS "{{SCHEMA}}".facts (
|
|
9
|
+
id BIGSERIAL PRIMARY KEY,
|
|
10
|
+
scope_key TEXT NOT NULL UNIQUE,
|
|
11
|
+
key TEXT NOT NULL,
|
|
12
|
+
value JSONB NOT NULL,
|
|
13
|
+
agent_id TEXT,
|
|
14
|
+
session_id TEXT,
|
|
15
|
+
shared BOOLEAN NOT NULL DEFAULT FALSE,
|
|
16
|
+
transient BOOLEAN NOT NULL DEFAULT FALSE,
|
|
17
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
18
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
19
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
20
|
+
CHECK (NOT (shared AND transient)),
|
|
21
|
+
|
|
22
|
+
-- Enrichment (EnhancedFactStore): all derived/rebuildable.
|
|
23
|
+
-- The embeddable/searchable text: key + the conventional value fields.
|
|
24
|
+
-- (immutable expression required for a generated column: || + coalesce)
|
|
25
|
+
search_text TEXT GENERATED ALWAYS AS (
|
|
26
|
+
coalesce(key, '')
|
|
27
|
+
|| ' ' || coalesce(value->>'name', '')
|
|
28
|
+
|| ' ' || coalesce(value->>'description', '')
|
|
29
|
+
|| ' ' || coalesce(value->>'text', '')
|
|
30
|
+
|| ' ' || coalesce(value->>'body', '')
|
|
31
|
+
|| ' ' || coalesce(value->>'subject', '')
|
|
32
|
+
) STORED,
|
|
33
|
+
content_hash TEXT, -- trigger-maintained (NOT generated:
|
|
34
|
+
-- a BEFORE trigger must read+reset on change)
|
|
35
|
+
last_crawled_at TIMESTAMPTZ, -- NULL ⇒ pending crawl
|
|
36
|
+
embedding vector({{EMBEDDING_DIM}}), -- NULL until embedded
|
|
37
|
+
embedded_at TIMESTAMPTZ,
|
|
38
|
+
embedding_model TEXT,
|
|
39
|
+
last_embedded_hash TEXT
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
-- Base indexes (SDK parity).
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_facts_key ON "{{SCHEMA}}".facts (key);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_facts_tags ON "{{SCHEMA}}".facts USING GIN (tags);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_facts_session ON "{{SCHEMA}}".facts (session_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_facts_agent ON "{{SCHEMA}}".facts (agent_id);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_facts_shared ON "{{SCHEMA}}".facts (shared);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_facts_transient ON "{{SCHEMA}}".facts (transient);
|
|
49
|
+
|
|
50
|
+
-- Work-queue indexes.
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_facts_uncrawled
|
|
52
|
+
ON "{{SCHEMA}}".facts (id) WHERE last_crawled_at IS NULL;
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_facts_needs_embedding
|
|
54
|
+
ON "{{SCHEMA}}".facts (id) WHERE embedding IS NULL;
|
|
55
|
+
|
|
56
|
+
-- Write-resets-pending-state (01-functional-spec §6.6, 03-design §2.1):
|
|
57
|
+
-- recompute content_hash from the embeddable content; when it changes,
|
|
58
|
+
-- (a) reset last_crawled_at → NULL so the harvester re-crawls, and
|
|
59
|
+
-- (b) leave last_embedded_hash stale so the embedder re-embeds.
|
|
60
|
+
-- Identical-content writes change NOTHING (C4).
|
|
61
|
+
CREATE OR REPLACE FUNCTION "{{SCHEMA}}".facts_touch() RETURNS trigger
|
|
62
|
+
LANGUAGE plpgsql AS $$
|
|
63
|
+
DECLARE
|
|
64
|
+
new_hash TEXT;
|
|
65
|
+
BEGIN
|
|
66
|
+
new_hash := md5(coalesce(NEW.key, '') || E'\x1f' || coalesce(NEW.value::text, ''));
|
|
67
|
+
IF TG_OP = 'INSERT' OR new_hash IS DISTINCT FROM OLD.content_hash THEN
|
|
68
|
+
NEW.content_hash := new_hash;
|
|
69
|
+
NEW.last_crawled_at := NULL;
|
|
70
|
+
END IF;
|
|
71
|
+
RETURN NEW;
|
|
72
|
+
END $$;
|
|
73
|
+
|
|
74
|
+
DROP TRIGGER IF EXISTS facts_touch ON "{{SCHEMA}}".facts;
|
|
75
|
+
CREATE TRIGGER facts_touch
|
|
76
|
+
BEFORE INSERT OR UPDATE ON "{{SCHEMA}}".facts
|
|
77
|
+
FOR EACH ROW EXECUTE FUNCTION "{{SCHEMA}}".facts_touch();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 0002_indexes — ANN index over facts.embedding + the lexical (BM25) index.
|
|
2
|
+
--
|
|
3
|
+
-- ANN: prefer pg_diskann (better recall/latency at scale on HorizonDB), fall
|
|
4
|
+
-- back to pgvector HNSW when pg_diskann is not allow-listed. Both share the
|
|
5
|
+
-- vector_cosine_ops opclass and the <=> operator, so the search procs are
|
|
6
|
+
-- identical either way.
|
|
7
|
+
--
|
|
8
|
+
-- Tokens: {{SCHEMA}}.
|
|
9
|
+
|
|
10
|
+
DO $$
|
|
11
|
+
BEGIN
|
|
12
|
+
BEGIN
|
|
13
|
+
CREATE EXTENSION IF NOT EXISTS pg_diskann CASCADE;
|
|
14
|
+
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_facts_embedding ON "{{SCHEMA}}".facts USING diskann (embedding vector_cosine_ops)';
|
|
15
|
+
EXCEPTION WHEN OTHERS THEN
|
|
16
|
+
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_facts_embedding ON "{{SCHEMA}}".facts USING hnsw (embedding vector_cosine_ops)';
|
|
17
|
+
END;
|
|
18
|
+
END $$;
|
|
19
|
+
|
|
20
|
+
-- Lexical (BM25) — pg_textsearch is a fail-fast precondition (01 §5.5).
|
|
21
|
+
-- Verified on live HorizonDB (pg_textsearch 1.3.0-dev): index AM `bm25`
|
|
22
|
+
-- requires WITH (text_config); `col <@> to_bm25query(q, idx)` returns the
|
|
23
|
+
-- NEGATIVE BM25 score (ascending order = best first; 0 = no match).
|
|
24
|
+
CREATE EXTENSION IF NOT EXISTS pg_textsearch;
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_facts_lexical
|
|
27
|
+
ON "{{SCHEMA}}".facts USING bm25 (search_text) WITH (text_config = 'english');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- 0003_age_bootstrap — AGE extension + the per-deployment graph.
|
|
2
|
+
--
|
|
3
|
+
-- Tokens: {{GRAPH_NAME}}. NOTE: create_graph() creates a Postgres schema named
|
|
4
|
+
-- after the graph, so the graph name MUST differ from the facts schema name.
|
|
5
|
+
|
|
6
|
+
CREATE EXTENSION IF NOT EXISTS age;
|
|
7
|
+
|
|
8
|
+
DO $$
|
|
9
|
+
BEGIN
|
|
10
|
+
IF NOT EXISTS (SELECT 1 FROM ag_catalog.ag_graph WHERE name = '{{GRAPH_NAME}}') THEN
|
|
11
|
+
PERFORM ag_catalog.create_graph('{{GRAPH_NAME}}');
|
|
12
|
+
END IF;
|
|
13
|
+
END $$;
|