@getrift/rift 0.1.0-beta.10 → 0.1.0-beta.11
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/src/cli/commands/search.d.ts.map +1 -1
- package/dist/src/cli/commands/search.js +28 -4
- package/dist/src/cli/commands/search.js.map +1 -1
- package/dist/src/cli/http-client.d.ts.map +1 -1
- package/dist/src/cli/http-client.js +21 -2
- package/dist/src/cli/http-client.js.map +1 -1
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js +27 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js.map +1 -0
- package/dist/src/ingestion/inbox-core/index.d.ts +1 -0
- package/dist/src/ingestion/inbox-core/index.d.ts.map +1 -1
- package/dist/src/ingestion/inbox-core/index.js +1 -0
- package/dist/src/ingestion/inbox-core/index.js.map +1 -1
- package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
- package/dist/src/jobs/handlers/ingest.js +184 -31
- package/dist/src/jobs/handlers/ingest.js.map +1 -1
- package/dist/src/mcp/errors.d.ts.map +1 -1
- package/dist/src/mcp/errors.js +20 -1
- package/dist/src/mcp/errors.js.map +1 -1
- package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
- package/dist/src/mcp/tools/context-pack.js +5 -2
- package/dist/src/mcp/tools/context-pack.js.map +1 -1
- package/dist/src/mcp/tools/search.d.ts +6 -2
- package/dist/src/mcp/tools/search.d.ts.map +1 -1
- package/dist/src/mcp/tools/search.js +34 -4
- package/dist/src/mcp/tools/search.js.map +1 -1
- package/dist/src/observability/tool-usage-stats.d.ts +62 -4
- package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
- package/dist/src/observability/tool-usage-stats.js +48 -27
- package/dist/src/observability/tool-usage-stats.js.map +1 -1
- package/dist/src/observability/tool-usage.d.ts +93 -0
- package/dist/src/observability/tool-usage.d.ts.map +1 -1
- package/dist/src/observability/tool-usage.js +124 -0
- package/dist/src/observability/tool-usage.js.map +1 -1
- package/dist/src/retrieval/compact.d.ts +115 -2
- package/dist/src/retrieval/compact.d.ts.map +1 -1
- package/dist/src/retrieval/compact.js +154 -5
- package/dist/src/retrieval/compact.js.map +1 -1
- package/dist/src/retrieval/context-pack.d.ts +8 -0
- package/dist/src/retrieval/context-pack.d.ts.map +1 -1
- package/dist/src/retrieval/context-pack.js +17 -2
- package/dist/src/retrieval/context-pack.js.map +1 -1
- package/dist/src/server/routes/mcp-usage.d.ts +4 -4
- package/dist/src/server/routes/search.d.ts.map +1 -1
- package/dist/src/server/routes/search.js +144 -24
- package/dist/src/server/routes/search.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,EAAE,CAiBxE;AAED,wBAAgB,iBAAiB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,EAAE,CAiBxE;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAiF3C"}
|
|
@@ -30,16 +30,32 @@ export function formatSearchResultsForCli(resp) {
|
|
|
30
30
|
export function makeSearchCommand() {
|
|
31
31
|
return new Command("search")
|
|
32
32
|
.description("Search across all indexed content")
|
|
33
|
-
|
|
33
|
+
// `[query]` is optional — `rift search --id <id> --detail middle|full`
|
|
34
|
+
// is a valid invocation that bypasses ranked retrieval entirely. The
|
|
35
|
+
// action handler rejects the empty case where neither query nor id is set.
|
|
36
|
+
.argument("[query]", "Search query")
|
|
34
37
|
.option("--scope <scope>", "Search scope (all|clients|projects|conversations|documents)")
|
|
35
38
|
.option("--client <name>", "Filter by client name")
|
|
36
39
|
.option("--since <date>", "Filter by date (ISO-8601)")
|
|
37
40
|
.option("--top-k <n>", "Max results", parseInt)
|
|
38
|
-
.option("--detail <level>", "Output detail: 'summary' (default, compact) or 'full' (
|
|
39
|
-
.option("--id <id>", "With --detail=full,
|
|
41
|
+
.option("--detail <level>", "Output detail: 'summary' (default, compact), 'middle' (bounded head+tail excerpt), or 'full' (raw content)")
|
|
42
|
+
.option("--id <id>", "With --detail=middle or full, fetch exactly this row by id")
|
|
43
|
+
.option("--tier <tier>", "With --id, scope the lookup to a tier (hot|cold|digest|document|structured_doc)")
|
|
44
|
+
.option("--source-table <name>", "With --id, the authoritative backing table (disambiguates rows shared across cloud/local)")
|
|
40
45
|
.action(async (query, opts, cmd) => {
|
|
41
46
|
const globalOpts = cmd.optsWithGlobals();
|
|
42
47
|
try {
|
|
48
|
+
// Exact-ID mode is recognised by `--id` with `--detail middle` or
|
|
49
|
+
// `--detail full`; everything else needs a positional query. We
|
|
50
|
+
// refuse here rather than send a bad shape that the daemon would
|
|
51
|
+
// reject — the trust contract is that the CLI is loud about
|
|
52
|
+
// precise-lookup misuse, not silent.
|
|
53
|
+
const isExactIdMode = !!opts.id && (opts.detail === "full" || opts.detail === "middle");
|
|
54
|
+
if (!query && !isExactIdMode) {
|
|
55
|
+
printError("Missing query. Pass a search query, or use `--id <id> --detail middle|full` for exact lookup.");
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
43
59
|
const baseUrl = resolveBaseUrl(globalOpts.config);
|
|
44
60
|
const token = await readToken();
|
|
45
61
|
if (!token) {
|
|
@@ -48,7 +64,11 @@ export function makeSearchCommand() {
|
|
|
48
64
|
return;
|
|
49
65
|
}
|
|
50
66
|
const client = createHttpClient({ baseUrl, token });
|
|
51
|
-
const body = {
|
|
67
|
+
const body = {};
|
|
68
|
+
// In exact-ID mode the daemon ignores `query` — omit it from the
|
|
69
|
+
// body so stdout / logs don't suggest a ranked search ran.
|
|
70
|
+
if (query && !isExactIdMode)
|
|
71
|
+
body.query = query;
|
|
52
72
|
if (opts.scope)
|
|
53
73
|
body.scope = opts.scope;
|
|
54
74
|
if (opts.client)
|
|
@@ -61,6 +81,10 @@ export function makeSearchCommand() {
|
|
|
61
81
|
body.detail = opts.detail;
|
|
62
82
|
if (opts.id)
|
|
63
83
|
body.id = opts.id;
|
|
84
|
+
if (opts.tier)
|
|
85
|
+
body.tier = opts.tier;
|
|
86
|
+
if (opts.sourceTable)
|
|
87
|
+
body.source_table = opts.sourceTable;
|
|
64
88
|
const { data } = await client.post("/search", body);
|
|
65
89
|
const resp = data;
|
|
66
90
|
if (globalOpts.json) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../../src/cli/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAsBhE;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAoB;IAC5D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CACR,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,gBAAgB,KAAK,CAAC,CAAC,KAAK,EAAE,CACxE,CAAC;QACF,IAAI,CAAC,CAAC,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;aACvC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;aACpC,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;SACzB,WAAW,CAAC,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../../src/cli/commands/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../../src/cli/http-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,YAAY,GACpB,oBAAoB,GACpB,cAAc,GACd,WAAW,GACX,SAAS,GACT,UAAU,GACV,cAAc,GACd,YAAY,GACZ,cAAc,GACd,SAAS,CAAC;AAEd,qBAAa,QAAS,SAAQ,KAAK;aAGf,IAAI,EAAE,YAAY;aAClB,UAAU,CAAC,EAAE,MAAM;aACnB,IAAI,CAAC,EAAE,OAAO;gBAH9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,YAAY,EAClB,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,IAAI,CAAC,EAAE,OAAO,YAAA;CAKjC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC7C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9C,aAAa,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC7C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC/D;
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../../src/cli/http-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,YAAY,GACpB,oBAAoB,GACpB,cAAc,GACd,WAAW,GACX,SAAS,GACT,UAAU,GACV,cAAc,GACd,YAAY,GACZ,cAAc,GACd,SAAS,CAAC;AAEd,qBAAa,QAAS,SAAQ,KAAK;aAGf,IAAI,EAAE,YAAY;aAClB,UAAU,CAAC,EAAE,MAAM;aACnB,IAAI,CAAC,EAAE,OAAO;gBAH9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,YAAY,EAClB,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,IAAI,CAAC,EAAE,OAAO,YAAA;CAKjC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC7C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9C,aAAa,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC7C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC/D;AA6ED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAqGjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAezE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAqBT;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC;IACxD,EAAE,EAAE,OAAO,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC,CAuBD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,GAAE,WAAyB,GAClC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqBxB"}
|
|
@@ -27,14 +27,33 @@ function isConnectionRefused(err) {
|
|
|
27
27
|
}
|
|
28
28
|
return false;
|
|
29
29
|
}
|
|
30
|
+
function hasTypedError(body) {
|
|
31
|
+
return (typeof body === "object" &&
|
|
32
|
+
body !== null &&
|
|
33
|
+
typeof body.error === "string");
|
|
34
|
+
}
|
|
35
|
+
function formatTypedBody(body) {
|
|
36
|
+
if (typeof body === "string")
|
|
37
|
+
return body;
|
|
38
|
+
return JSON.stringify(body);
|
|
39
|
+
}
|
|
30
40
|
function mapStatusToError(status, body) {
|
|
31
41
|
switch (status) {
|
|
32
42
|
case 401:
|
|
33
43
|
return new CliError("Authentication failed. Run: rift token issue", "unauthorized", status, body);
|
|
34
44
|
case 404:
|
|
35
|
-
|
|
45
|
+
// Daemon-level 404 (no such endpoint) vs. response-level 404 with a
|
|
46
|
+
// typed `error` field (e.g. id_not_found). Surface the typed body so
|
|
47
|
+
// callers don't read "Endpoint not available" and route around us.
|
|
48
|
+
return new CliError(hasTypedError(body)
|
|
49
|
+
? `Not found: ${formatTypedBody(body)}`
|
|
50
|
+
: "Endpoint not available", "not_found", status, body);
|
|
36
51
|
case 409:
|
|
37
|
-
|
|
52
|
+
// Same idea: prefer the typed body (e.g. ambiguous_id) over the
|
|
53
|
+
// generic exclusive-operation message that only applies to jobs.
|
|
54
|
+
return new CliError(hasTypedError(body)
|
|
55
|
+
? `Conflict: ${formatTypedBody(body)}`
|
|
56
|
+
: "Another exclusive operation is running", "conflict", status, body);
|
|
38
57
|
case 429:
|
|
39
58
|
return new CliError("Rate limited. Retry later.", "rate_limited", status, body);
|
|
40
59
|
default:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../../src/cli/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,WAAW,EACX,mBAAmB,GAEpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAa7D,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IACA;IACA;IAJlB,YACE,OAAe,EACC,IAAkB,EAClB,UAAmB,EACnB,IAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAc;QAClB,eAAU,GAAV,UAAU,CAAS;QACnB,SAAI,GAAJ,IAAI,CAAU;QAG9B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAuBD,SAAS,mBAAmB,CAAC,GAAY;IACvC,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,KAAK,GAAI,GAAqC,CAAC,KAAK,CAAC;QAC3D,IAAI,KAAK,EAAE,IAAI,KAAK,cAAc;YAAE,OAAO,IAAI,CAAC;QAChD,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,CAAC;QAC7C,IAAI,IAAI,KAAK,cAAc;YAAE,OAAO,IAAI,CAAC;IAC3C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc,EAAE,IAAa;IACrD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,GAAG;YACN,OAAO,IAAI,QAAQ,CACjB,8CAA8C,EAC9C,cAAc,EACd,MAAM,EACN,IAAI,CACL,CAAC;QACJ,KAAK,GAAG;YACN,OAAO,IAAI,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../../src/cli/http-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-fingerprint.d.ts","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/conversation-fingerprint.ts"],"names":[],"mappings":"AAwBA,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical content fingerprint for a parsed conversation.
|
|
3
|
+
*
|
|
4
|
+
* Pure: takes the parser-emitted role-prefixed conversation text and
|
|
5
|
+
* returns a stable hex sha256. No filesystem, no clock, no random input.
|
|
6
|
+
*
|
|
7
|
+
* Why content-only:
|
|
8
|
+
* The fingerprint must change iff the conversation's substance changes.
|
|
9
|
+
* Title / createdAt are stable across re-exports of the same provider
|
|
10
|
+
* conversation, so they are not part of the input. Including them would
|
|
11
|
+
* produce false "grew" signals on metadata-only refreshes.
|
|
12
|
+
*
|
|
13
|
+
* Why role-prefixed text:
|
|
14
|
+
* Every parser already normalises its source format into the same
|
|
15
|
+
* `content` shape (`User: ...\n\nAssistant: ...`). That makes the
|
|
16
|
+
* fingerprint source-agnostic — two parsers producing identical
|
|
17
|
+
* content for the same conversation hash to the same value.
|
|
18
|
+
*
|
|
19
|
+
* Used by the ingest handler's refresh-on-grow logic: if a re-imported
|
|
20
|
+
* conversation has the same content as the version we already stored,
|
|
21
|
+
* skip it; if it has new messages appended, refresh the row.
|
|
22
|
+
*/
|
|
23
|
+
import crypto from "node:crypto";
|
|
24
|
+
export function conversationContentFingerprint(content) {
|
|
25
|
+
return crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=conversation-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-fingerprint.js","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/conversation-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,UAAU,8BAA8B,CAAC,OAAe;IAC5D,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { inboxIdempotencyKey } from "./idempotency.js";
|
|
14
14
|
export { stableConversationRowId } from "./conversation-key.js";
|
|
15
|
+
export { conversationContentFingerprint } from "./conversation-fingerprint.js";
|
|
15
16
|
export { detectSourceFromSubdir } from "./source-detection.js";
|
|
16
17
|
export { SUPPORTED_INBOX_EXTENSIONS, isSupportedInboxExtension, } from "./extensions.js";
|
|
17
18
|
export { sniffInboxSource, type SniffedProvider } from "./source-sniffer.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { inboxIdempotencyKey } from "./idempotency.js";
|
|
14
14
|
export { stableConversationRowId } from "./conversation-key.js";
|
|
15
|
+
export { conversationContentFingerprint } from "./conversation-fingerprint.js";
|
|
15
16
|
export { detectSourceFromSubdir } from "./source-detection.js";
|
|
16
17
|
export { SUPPORTED_INBOX_EXTENSIONS, isSupportedInboxExtension, } from "./extensions.js";
|
|
17
18
|
export { sniffInboxSource } from "./source-sniffer.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAwB,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/ingestion/inbox-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAwB,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../../src/jobs/handlers/ingest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../../src/jobs/handlers/ingest.ts"],"names":[],"mappings":"AAsDA,OAAO,KAAK,EAAO,UAAU,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAElB,MAAM,0BAA0B,CAAC;AAalC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAKnE,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,CAAC;IAChB,wBAAwB,CAAC,EAAE,CACzB,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,iBAAiB,KACpB,iBAAiB,CAAC;CACxB;AAiHD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CA4PvE"}
|
|
@@ -11,17 +11,43 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Phase 2 — Persist (only if all conversations passed Phase 1):
|
|
13
13
|
* 5. Write raw files (with full metadata — rebuild-safe)
|
|
14
|
-
* 6. Insert all rows into conversations_hot
|
|
14
|
+
* 6. Insert all rows into conversations_hot (or refresh into the
|
|
15
|
+
* table where an older snapshot already lives)
|
|
15
16
|
*
|
|
16
17
|
* This two-phase design ensures atomicity: either all conversations in
|
|
17
18
|
* an upload are indexed, or none are. No partial ingest.
|
|
18
19
|
*
|
|
19
20
|
* Replay safety:
|
|
20
21
|
* - Row ID is deterministic on (source, provider conversation ID), so a
|
|
21
|
-
* re-imported monthly export collapses to the same row across jobs
|
|
22
|
-
* - Raw files are written before table insert (crash safety)
|
|
23
|
-
* - Existing rows in conversations_hot OR conversations_cold are
|
|
24
|
-
* on replay (compacted-then-re-imported conversations don't re-enter hot)
|
|
22
|
+
* re-imported monthly export collapses to the same row across jobs.
|
|
23
|
+
* - Raw files are written before table insert (crash safety).
|
|
24
|
+
* - Existing rows in conversations_hot OR conversations_cold are checked
|
|
25
|
+
* on replay (compacted-then-re-imported conversations don't re-enter hot).
|
|
26
|
+
*
|
|
27
|
+
* Refresh-on-grow:
|
|
28
|
+
* - For each existing row, the handler compares the incoming canonical
|
|
29
|
+
* `conversationContentFingerprint(content)` against a fingerprint
|
|
30
|
+
* derived from the existing row's own `content` column. The row, not
|
|
31
|
+
* the raw artifact, is the source of truth for "what is currently
|
|
32
|
+
* indexed"; using raw here would create a drift bug if Phase 2 crashes
|
|
33
|
+
* between writing the new raw and replacing the row. If fingerprints
|
|
34
|
+
* match, the conversation is unchanged and we skip. If they differ,
|
|
35
|
+
* the conversation has grown and we refresh: write a new raw artifact
|
|
36
|
+
* (which records the target table so crash recovery can route the
|
|
37
|
+
* restored row back to the right place), delete the old row, insert
|
|
38
|
+
* the new row in the same table where it lived (hot stays hot, cold
|
|
39
|
+
* stays cold — we do not silently re-promote). After a successful
|
|
40
|
+
* row replacement, prior raw artifacts for that row id are pruned
|
|
41
|
+
* best-effort so rebuild does not insert duplicates.
|
|
42
|
+
*
|
|
43
|
+
* Schema caveat (deferred):
|
|
44
|
+
* - `conversation_fingerprint` is persisted in raw artifacts only.
|
|
45
|
+
* Adding it as a LanceDB column would require a schema migration
|
|
46
|
+
* across both `conversations_hot` and `conversations_cold` plus the
|
|
47
|
+
* rebuild / shadow-swap paths, which is non-trivial and out of scope
|
|
48
|
+
* for this slice. The row's stored `content` remains the comparison
|
|
49
|
+
* source until that migration lands; the raw fingerprint is kept
|
|
50
|
+
* for forward-compat and external tooling.
|
|
25
51
|
*/
|
|
26
52
|
import crypto from "node:crypto";
|
|
27
53
|
import fs from "node:fs";
|
|
@@ -34,6 +60,7 @@ import { parseClaudeWeb } from "../../ingestion/parsers/claude-web.js";
|
|
|
34
60
|
import { parseGeminiWeb } from "../../ingestion/parsers/gemini-web.js";
|
|
35
61
|
import { parseGrokWeb } from "../../ingestion/parsers/grok-web.js";
|
|
36
62
|
import { stableConversationRowId } from "../../ingestion/inbox-core/conversation-key.js";
|
|
63
|
+
import { conversationContentFingerprint } from "../../ingestion/inbox-core/conversation-fingerprint.js";
|
|
37
64
|
import { CodexCliMetadataExtractor } from "../../providers/codex-cli-metadata-extraction.js";
|
|
38
65
|
import { recordEmbed } from "../../observability/embedding-events.js";
|
|
39
66
|
import { recordIndexWrite } from "../../observability/index-events.js";
|
|
@@ -60,14 +87,71 @@ function resolveChainRoot(job, queue) {
|
|
|
60
87
|
return current;
|
|
61
88
|
}
|
|
62
89
|
/**
|
|
63
|
-
* Find the raw file for `rowId` under `rawDir`, if
|
|
90
|
+
* Find the latest raw file for `rowId` under `rawDir`, if any.
|
|
91
|
+
*
|
|
92
|
+
* Raw filenames are `<timestamp>_<rowId>.json`. After a refresh, the
|
|
93
|
+
* same row id can have multiple files (one per version); we pick the
|
|
94
|
+
* lexicographically greatest filename, which matches the ISO-8601
|
|
95
|
+
* timestamp ordering used at write time.
|
|
64
96
|
*/
|
|
65
97
|
function findRawFile(rawDir, rowId) {
|
|
66
98
|
if (!fs.existsSync(rawDir))
|
|
67
99
|
return undefined;
|
|
68
100
|
const suffix = `_${rowId}.json`;
|
|
69
|
-
const
|
|
70
|
-
|
|
101
|
+
const matches = fs.readdirSync(rawDir).filter((f) => f.endsWith(suffix));
|
|
102
|
+
if (matches.length === 0)
|
|
103
|
+
return undefined;
|
|
104
|
+
matches.sort();
|
|
105
|
+
return path.join(rawDir, matches[matches.length - 1]);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Compute the canonical content fingerprint of an existing indexed row.
|
|
109
|
+
*
|
|
110
|
+
* The row's own `content` is the source of truth for "what is currently
|
|
111
|
+
* indexed". Using the row content (rather than the raw artifact on disk)
|
|
112
|
+
* avoids a raw-vs-row drift bug: if Phase 2 crashes between writing the
|
|
113
|
+
* new raw and replacing the row, the raw on disk is ahead of the table,
|
|
114
|
+
* and a subsequent re-import comparing against raw would mistakenly
|
|
115
|
+
* decide "unchanged" while the row is still stale.
|
|
116
|
+
*
|
|
117
|
+
* Returns `undefined` if the row has no readable content; callers
|
|
118
|
+
* treat that as "cannot prove unchanged" and refresh.
|
|
119
|
+
*/
|
|
120
|
+
function existingRowFingerprint(row) {
|
|
121
|
+
const content = typeof row.content === "string" ? row.content : undefined;
|
|
122
|
+
if (!content)
|
|
123
|
+
return undefined;
|
|
124
|
+
return conversationContentFingerprint(content);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Best-effort prune of stale raw artifacts for `rowId` under `rawDir`,
|
|
128
|
+
* keeping only `keepFileName`. Called after a refresh has successfully
|
|
129
|
+
* replaced the row in its table, so the new raw is the only one rebuild
|
|
130
|
+
* needs to re-derive the row.
|
|
131
|
+
*
|
|
132
|
+
* Safe-by-design: any pre-existing raw still on disk has older content
|
|
133
|
+
* than the row now holds, so deleting it loses nothing the table cannot
|
|
134
|
+
* already reconstruct. If a prune fails (filesystem error, race with
|
|
135
|
+
* another process), we swallow the error rather than fail the ingest —
|
|
136
|
+
* the rebuild duplicate caused by an extra raw is a much cheaper bug
|
|
137
|
+
* than a failed ingest after the row is already correct.
|
|
138
|
+
*/
|
|
139
|
+
function pruneStaleRawArtifacts(rawDir, rowId, keepFileName) {
|
|
140
|
+
if (!fs.existsSync(rawDir))
|
|
141
|
+
return;
|
|
142
|
+
const suffix = `_${rowId}.json`;
|
|
143
|
+
for (const name of fs.readdirSync(rawDir)) {
|
|
144
|
+
if (!name.endsWith(suffix))
|
|
145
|
+
continue;
|
|
146
|
+
if (name === keepFileName)
|
|
147
|
+
continue;
|
|
148
|
+
try {
|
|
149
|
+
fs.unlinkSync(path.join(rawDir, name));
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// best-effort
|
|
153
|
+
}
|
|
154
|
+
}
|
|
71
155
|
}
|
|
72
156
|
export function createIngestHandler(deps) {
|
|
73
157
|
return async (job) => {
|
|
@@ -108,29 +192,50 @@ export function createIngestHandler(deps) {
|
|
|
108
192
|
// re-imported monthly export collapses to the same row instead
|
|
109
193
|
// of duplicating every conversation under a fresh job-root id.
|
|
110
194
|
const rowId = stableConversationRowId(payload.source, conversation.id);
|
|
111
|
-
|
|
195
|
+
const fingerprint = conversationContentFingerprint(conversation.content);
|
|
196
|
+
// Replay / refresh guard.
|
|
112
197
|
// Check both hot AND cold — a conversation compacted into cold
|
|
113
198
|
// is exactly the kind of "old" conversation a monthly full export
|
|
114
199
|
// re-includes; without the cold check we'd duplicate it back into hot.
|
|
200
|
+
// For each existing row, compare content fingerprints to decide
|
|
201
|
+
// between skip (unchanged) and refresh (grown).
|
|
115
202
|
const existingHot = await table
|
|
116
203
|
.query()
|
|
117
204
|
.where(`id = '${rowId}'`)
|
|
118
205
|
.toArray();
|
|
119
|
-
|
|
120
|
-
|
|
206
|
+
const existingCold = existingHot.length === 0
|
|
207
|
+
? await coldTable.query().where(`id = '${rowId}'`).toArray()
|
|
208
|
+
: [];
|
|
209
|
+
const existingTable = existingHot.length > 0
|
|
210
|
+
? "conversations_hot"
|
|
211
|
+
: existingCold.length > 0
|
|
212
|
+
? "conversations_cold"
|
|
213
|
+
: undefined;
|
|
214
|
+
if (existingTable) {
|
|
215
|
+
// Row is already indexed. Decide skip vs refresh by comparing
|
|
216
|
+
// the incoming content fingerprint against a fingerprint
|
|
217
|
+
// derived from the row's own content. Using the row (not the
|
|
218
|
+
// raw artifact on disk) sidesteps drift between raw and row
|
|
219
|
+
// during the Phase 2 crash window.
|
|
220
|
+
const existingRow = (existingHot[0] ?? existingCold[0]);
|
|
221
|
+
const existingFingerprint = existingRowFingerprint(existingRow);
|
|
222
|
+
if (existingFingerprint && existingFingerprint === fingerprint) {
|
|
223
|
+
// Unchanged — preserve the existing snapshot.
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// Either the conversation has grown (fingerprint differs) or
|
|
227
|
+
// we cannot prove it's unchanged (row content is empty / not
|
|
228
|
+
// a string). In both cases, fall through to refresh:
|
|
229
|
+
// re-extract metadata + re-embed, then in Phase 2 replace
|
|
230
|
+
// the row in the table where it currently lives.
|
|
121
231
|
}
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Crash recovery: raw file exists but row missing → defer to Phase 2.
|
|
130
|
-
const existingRawPath = findRawFile(rawDir, rowId);
|
|
131
|
-
if (existingRawPath) {
|
|
132
|
-
recoverable.push({ rawPath: existingRawPath, rowId });
|
|
133
|
-
continue;
|
|
232
|
+
else {
|
|
233
|
+
// Crash recovery: raw file exists but row missing → defer to Phase 2.
|
|
234
|
+
const existingRawPath = findRawFile(rawDir, rowId);
|
|
235
|
+
if (existingRawPath) {
|
|
236
|
+
recoverable.push({ rawPath: existingRawPath, rowId });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
134
239
|
}
|
|
135
240
|
let metadata;
|
|
136
241
|
try {
|
|
@@ -152,7 +257,14 @@ export function createIngestHandler(deps) {
|
|
|
152
257
|
await quarantine(deps.dataDir, payload.source, conversation, err);
|
|
153
258
|
throw err;
|
|
154
259
|
}
|
|
155
|
-
processed.push({
|
|
260
|
+
processed.push({
|
|
261
|
+
conversation,
|
|
262
|
+
rowId,
|
|
263
|
+
fingerprint,
|
|
264
|
+
metadata,
|
|
265
|
+
embedding,
|
|
266
|
+
...(existingTable ? { refreshTable: existingTable } : {}),
|
|
267
|
+
});
|
|
156
268
|
}
|
|
157
269
|
if (processed.length === 0 && recoverable.length === 0) {
|
|
158
270
|
return; // All already indexed (replay)
|
|
@@ -161,14 +273,22 @@ export function createIngestHandler(deps) {
|
|
|
161
273
|
// Write raw files first (crash safety), then insert rows.
|
|
162
274
|
// Recoverable items already have raw files — only insert rows.
|
|
163
275
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
276
|
+
const newRawFileNames = new Map();
|
|
164
277
|
for (const item of processed) {
|
|
165
278
|
const extractionProvenance = metadataExtractor.getProvenance?.();
|
|
279
|
+
// `target_table` lets crash recovery route the restored row to the
|
|
280
|
+
// table the slice originally targeted. Without it, a cold refresh
|
|
281
|
+
// interrupted between delete-old and add-new would be restored
|
|
282
|
+
// into `conversations_hot` on replay (silent re-promote).
|
|
283
|
+
const targetTable = item.refreshTable ?? "conversations_hot";
|
|
166
284
|
const rawData = {
|
|
167
285
|
source: payload.source,
|
|
168
286
|
conversation_id: item.conversation.id,
|
|
169
287
|
title: item.conversation.title,
|
|
170
288
|
created_at: item.conversation.createdAt,
|
|
171
289
|
content: item.conversation.content,
|
|
290
|
+
conversation_fingerprint: item.fingerprint,
|
|
291
|
+
target_table: targetTable,
|
|
172
292
|
summary: item.conversation.title,
|
|
173
293
|
domain: item.metadata.domain,
|
|
174
294
|
intent: item.metadata.intent,
|
|
@@ -182,10 +302,24 @@ export function createIngestHandler(deps) {
|
|
|
182
302
|
: {}),
|
|
183
303
|
};
|
|
184
304
|
const rawFileName = `${timestamp}_${item.rowId}.json`;
|
|
305
|
+
newRawFileNames.set(item.rowId, rawFileName);
|
|
185
306
|
const rawPath = path.join(rawDir, rawFileName);
|
|
186
307
|
await atomicWrite(rawPath, JSON.stringify(rawData, null, 2));
|
|
187
308
|
}
|
|
188
309
|
for (const item of processed) {
|
|
310
|
+
// For a refresh, delete the old row from whichever table it lives
|
|
311
|
+
// in BEFORE inserting the new one — otherwise LanceDB ends up with
|
|
312
|
+
// two rows sharing the same id. Raw artifact for the new version
|
|
313
|
+
// is already on disk above, so if the delete-then-add window is
|
|
314
|
+
// interrupted, the next replay finds rowId missing + raw present
|
|
315
|
+
// and reindexes from raw via the recoverable path.
|
|
316
|
+
const targetTableName = item.refreshTable ?? "conversations_hot";
|
|
317
|
+
const targetTable = item.refreshTable === "conversations_cold"
|
|
318
|
+
? coldTable
|
|
319
|
+
: table;
|
|
320
|
+
if (item.refreshTable) {
|
|
321
|
+
await targetTable.delete(`id = '${item.rowId}'`);
|
|
322
|
+
}
|
|
189
323
|
const row = {
|
|
190
324
|
id: item.rowId,
|
|
191
325
|
content: item.conversation.content,
|
|
@@ -202,15 +336,25 @@ export function createIngestHandler(deps) {
|
|
|
202
336
|
idempotency_key: payload.idempotency_key ?? "",
|
|
203
337
|
};
|
|
204
338
|
await recordIndexWrite(deps.dataDir, {
|
|
205
|
-
table:
|
|
339
|
+
table: targetTableName,
|
|
206
340
|
pipeline: "ingest_job",
|
|
207
|
-
operation:
|
|
341
|
+
operation: item.refreshTable
|
|
342
|
+
? "conversation_refresh"
|
|
343
|
+
: "conversation_upsert",
|
|
208
344
|
row_count: 1,
|
|
209
|
-
}, () =>
|
|
345
|
+
}, () => targetTable.add([row]));
|
|
346
|
+
if (item.refreshTable) {
|
|
347
|
+
// Once the new row is in place, the prior raw artifact(s) for
|
|
348
|
+
// this rowId only carry the older content. Prune them so a
|
|
349
|
+
// future rebuild does not insert duplicate rows (one per raw)
|
|
350
|
+
// for the same conversation. Best-effort by design.
|
|
351
|
+
const keep = newRawFileNames.get(item.rowId);
|
|
352
|
+
pruneStaleRawArtifacts(rawDir, item.rowId, keep);
|
|
353
|
+
}
|
|
210
354
|
}
|
|
211
355
|
// Recover rows from existing raw files (crash recovery).
|
|
212
356
|
for (const item of recoverable) {
|
|
213
|
-
await reindexFromRaw(item.rawPath, item.rowId, payload, table, deps.embeddingProvider, deps.dataDir);
|
|
357
|
+
await reindexFromRaw(item.rawPath, item.rowId, payload, { hot: table, cold: coldTable }, deps.embeddingProvider, deps.dataDir);
|
|
214
358
|
}
|
|
215
359
|
};
|
|
216
360
|
}
|
|
@@ -224,11 +368,20 @@ function resolveMetadataExtractor(payload, deps) {
|
|
|
224
368
|
* Re-index a conversation from its raw file (crash recovery).
|
|
225
369
|
* Raw files written by Phase 2 include full metadata, so we can
|
|
226
370
|
* rebuild the row without calling the extractor again.
|
|
371
|
+
*
|
|
372
|
+
* Routing: the raw artifact's `target_table` decides hot vs cold so
|
|
373
|
+
* a cold refresh interrupted between delete-old and add-new is
|
|
374
|
+
* restored back into cold, not silently re-promoted to hot. Legacy
|
|
375
|
+
* artifacts (pre-2026-05-14) have no `target_table` field and default
|
|
376
|
+
* to hot — same behavior as before this slice.
|
|
227
377
|
*/
|
|
228
|
-
async function reindexFromRaw(rawPath, rowId, payload,
|
|
378
|
+
async function reindexFromRaw(rawPath, rowId, payload, tables, embeddingProvider, dataDir) {
|
|
229
379
|
const raw = JSON.parse(fs.readFileSync(rawPath, "utf-8"));
|
|
230
380
|
const content = raw.content ?? "";
|
|
231
381
|
const summary = raw.summary ?? "";
|
|
382
|
+
const rawTarget = raw.target_table;
|
|
383
|
+
const targetTableName = rawTarget === "conversations_cold" ? "conversations_cold" : "conversations_hot";
|
|
384
|
+
const targetTable = targetTableName === "conversations_cold" ? tables.cold : tables.hot;
|
|
232
385
|
const embedding = await recordEmbed(dataDir, embeddingProvider, {
|
|
233
386
|
pipeline: "ingest_job",
|
|
234
387
|
operation: "reindex_from_raw",
|
|
@@ -250,11 +403,11 @@ async function reindexFromRaw(rawPath, rowId, payload, table, embeddingProvider,
|
|
|
250
403
|
idempotency_key: payload.idempotency_key ?? "",
|
|
251
404
|
};
|
|
252
405
|
await recordIndexWrite(dataDir, {
|
|
253
|
-
table:
|
|
406
|
+
table: targetTableName,
|
|
254
407
|
pipeline: "ingest_job",
|
|
255
408
|
operation: "conversation_reindex_from_raw",
|
|
256
409
|
row_count: 1,
|
|
257
|
-
}, () =>
|
|
410
|
+
}, () => targetTable.add([row]));
|
|
258
411
|
}
|
|
259
412
|
/**
|
|
260
413
|
* Quarantine a failed conversation to data/quarantine/.
|