@futdevpro/fdp-agent-memory 0.1.0
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 +345 -0
- package/build/package.json +96 -0
- package/build/src/_assets/mcp-client-config/README.md +29 -0
- package/build/src/_assets/mcp-client-config/claude_desktop_config.json +15 -0
- package/build/src/_assets/mcp-client-config/mcp.json +15 -0
- package/build/src/_collections/config-catalog.const.js +180 -0
- package/build/src/_collections/config-error-codes.const.js +30 -0
- package/build/src/_collections/config-presets.const.js +25 -0
- package/build/src/_collections/error-banners.const.js +100 -0
- package/build/src/_collections/error-codes.const.js +150 -0
- package/build/src/_collections/fam-db-models.const.js +37 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +80 -0
- package/build/src/_collections/fam-error-context.util.js +90 -0
- package/build/src/_collections/fam-error-factory.util.js +64 -0
- package/build/src/_enums/fam-config-level.type-enum.js +15 -0
- package/build/src/_enums/fam-table.type-enum.js +20 -0
- package/build/src/_integration-tests/_helpers/fam-integration-test-setup.util.js +105 -0
- package/build/src/_models/data-models/fam-codebase.data-model.js +51 -0
- package/build/src/_models/data-models/fam-coding-patterns.data-model.js +58 -0
- package/build/src/_models/data-models/fam-config.data-model.js +68 -0
- package/build/src/_models/data-models/fam-documents.data-model.js +53 -0
- package/build/src/_models/data-models/fam-entry-base-properties.const.js +43 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +81 -0
- package/build/src/_models/data-models/fam-error.data-model.js +88 -0
- package/build/src/_models/data-models/fam-ingest-run.data-model.js +74 -0
- package/build/src/_models/data-models/fam-knowledge.data-model.js +48 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +55 -0
- package/build/src/_models/data-models/fam-reference.data-model.js +67 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +51 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +52 -0
- package/build/src/_models/interfaces/fam-common.interface.js +23 -0
- package/build/src/_models/interfaces/fam-config.interface.js +2 -0
- package/build/src/_models/interfaces/fam-error.interface.js +2 -0
- package/build/src/_modules/embedding/_collections/fam-embedding-pricing.const.js +22 -0
- package/build/src/_modules/embedding/_collections/fam-store-registry.const.js +63 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-cost.interface.js +10 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-resolved-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +52 -0
- package/build/src/_modules/embedding/_services/fam-embedding-cost.control-service.js +175 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +202 -0
- package/build/src/_modules/embedding/_services/fam-embedding-preset.control-service.js +66 -0
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +253 -0
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +64 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +112 -0
- package/build/src/_modules/embedding/_services/fam-mock-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-openai-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +244 -0
- package/build/src/_modules/embedding/index.js +40 -0
- package/build/src/_modules/ingest/_collections/fam-content-hash.util.js +35 -0
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +95 -0
- package/build/src/_modules/ingest/_collections/fam-glob-match.util.js +84 -0
- package/build/src/_modules/ingest/_collections/fam-md-chunker.util.js +164 -0
- package/build/src/_modules/ingest/_collections/fam-scan-path.util.js +91 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +54 -0
- package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +76 -0
- package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +316 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-ingest.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +114 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +74 -0
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +85 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +384 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +211 -0
- package/build/src/_modules/ingest/index.js +46 -0
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +186 -0
- package/build/src/_modules/mcp/_models/interfaces/fam-mcp.interface.js +31 -0
- package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +111 -0
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +1180 -0
- package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +123 -0
- package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +69 -0
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +99 -0
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +460 -0
- package/build/src/_modules/mcp/index.js +35 -0
- package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +166 -0
- package/build/src/_modules/migration/_collections/fam-import-content-hash.util.js +38 -0
- package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +90 -0
- package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +20 -0
- package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +26 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-export-reader.service.js +134 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +533 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-sqlite-reader.service.js +144 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-worker-reader.service.js +115 -0
- package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +102 -0
- package/build/src/_modules/migration/index.js +38 -0
- package/build/src/_modules/retrieval/_models/interfaces/fam-retrieval.interface.js +2 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +67 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-suggestions.util.js +182 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +282 -0
- package/build/src/_modules/retrieval/index.js +22 -0
- package/build/src/_modules/scope-reference/_collections/fam-fuzzy-match.util.js +86 -0
- package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +47 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-reference-resolution.interface.js +2 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-resolution-trace.interface.js +2 -0
- package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +179 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +473 -0
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +215 -0
- package/build/src/_modules/scope-reference/index.js +26 -0
- package/build/src/_routes/server/api/api.controller.js +400 -0
- package/build/src/_routes/server/client-app/client-app.control-service.js +132 -0
- package/build/src/_routes/server/client-app/client-app.controller.js +35 -0
- package/build/src/_routes/server/config/config.control-service.js +476 -0
- package/build/src/_routes/server/config/config.data-service.js +49 -0
- package/build/src/_routes/server/errors/errors.control-service.js +123 -0
- package/build/src/_routes/server/errors/errors.controller.js +65 -0
- package/build/src/_routes/server/errors/errors.data-service.js +80 -0
- package/build/src/_routes/server/server-status/server-status.control-service.js +19 -0
- package/build/src/_routes/server/server-status/server-status.controller.js +39 -0
- package/build/src/app.server.js +122 -0
- package/build/src/environments/environment.js +20 -0
- package/build/src/index.js +18 -0
- package/client-dist/chunk-GHKRM4SM.js +1 -0
- package/client-dist/chunk-LMTL7GA3.js +575 -0
- package/client-dist/index.html +17 -0
- package/client-dist/main-2KWB3QYK.js +2 -0
- package/client-dist/polyfills-HGDOEU5L.js +2 -0
- package/client-dist/styles-3J7JD5YE.css +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_DeltaCompare_Util = void 0;
|
|
4
|
+
const fam_content_hash_util_1 = require("../_collections/fam-content-hash.util");
|
|
5
|
+
/**
|
|
6
|
+
* `FAM_DeltaCompare_Util` (SP-4.3, dsgn-004 §4, **BFR-AM-006 workaround**) — az idempotens
|
|
7
|
+
* delta-detection. A `(sourceFilePath, chunkIndex)` kulcs + `contentHash` alapján veti össze az ÚJ
|
|
8
|
+
* batch chunkjait a táron MÁR meglévő (nem soft-deleted) chunkokkal, és `equal | modified | new |
|
|
9
|
+
* deleted` verdiktet ad (dsgn-004 §4.2):
|
|
10
|
+
*
|
|
11
|
+
* - `new` : a `(fájl, index)` kulcs még nem volt a táron → insert új embeddinggel.
|
|
12
|
+
* - `equal` : a kulcs létezik ÉS a `contentHash` egyezik → **skip** (NINCS re-embed — ez az
|
|
13
|
+
* embedding-költség fő minimalizálója).
|
|
14
|
+
* - `modified` : a kulcs létezik, de a `contentHash` eltér → re-embed + `$set`.
|
|
15
|
+
* - `deleted` : a táron volt chunk, amit az új batch MÁR NEM tartalmaz (a fájl rövidült/törölt)
|
|
16
|
+
* → soft-delete (a `chunk` ekkor `null`, az `existingId` a soft-deletelendő `_id`).
|
|
17
|
+
*
|
|
18
|
+
* **BFR-AM-006 (dsgn-004 §4 megjegyzés):** a CCAP `RAG_DataCompare_Util.compareData` (deep-equal a
|
|
19
|
+
* nem-metadata mezőkön) generikus alternatíva; MVP1-ben a contentHash-alapú összevetés elég (a
|
|
20
|
+
* `content` a vektorizálandó kanonikus szöveg, így a hash dönt). MEGJEGYZÉS: a `DyNTS_DataService`
|
|
21
|
+
* base mostanra (`nts-dynamo@1.15.57`) shippeli a generikus `compareData(new, old, options?)`-t —
|
|
22
|
+
* a BFR-AM-006 bedrock-oldala TEHÁT MÁR ELÉRHETŐ; ám a delta-detection szándékosan a
|
|
23
|
+
* contentHash-kulcson marad (a chunk-azonosság a `(fájl, index)` kulcs + a normalizált
|
|
24
|
+
* tartalom-hash, NEM a mező-szintű deep-equal — ez a dsgn-004 §4.1 szerinti SSOT). Statikus util.
|
|
25
|
+
*/
|
|
26
|
+
class FAM_DeltaCompare_Util {
|
|
27
|
+
/**
|
|
28
|
+
* A delta-verdiktek egy fájl ÚJ chunk-batch-ére (dsgn-004 §4.2). Az `existing` a táron meglévő
|
|
29
|
+
* (nem soft-deleted) `FAM_Entry`-k az adott `sourceFilePath`-ra; a kulcs a `chunkIndex`. A
|
|
30
|
+
* batch után minden olyan `existing` kulcs, amire NINCS új chunk → `deleted`.
|
|
31
|
+
*
|
|
32
|
+
* A `contentHash`-t a util itt számolja az ÚJ chunkokra (`FAM_ContentHash_Util.hash`,
|
|
33
|
+
* normalizált) — az `existing.contentHash` a táron tárolt érték.
|
|
34
|
+
*/
|
|
35
|
+
static compare(newChunks, existing) {
|
|
36
|
+
const existingByIndex = new Map();
|
|
37
|
+
for (const entry of existing) {
|
|
38
|
+
if (typeof entry.chunkIndex === 'number') {
|
|
39
|
+
existingByIndex.set(entry.chunkIndex, entry);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const items = [];
|
|
43
|
+
const matchedIndices = new Set();
|
|
44
|
+
// (1) Az új chunkok verdiktje (new / equal / modified).
|
|
45
|
+
for (const chunk of newChunks) {
|
|
46
|
+
const contentHash = fam_content_hash_util_1.FAM_ContentHash_Util.hash(chunk.content);
|
|
47
|
+
const existingEntry = existingByIndex.get(chunk.chunkIndex);
|
|
48
|
+
if (!existingEntry) {
|
|
49
|
+
items.push({ verdict: 'new', chunk: chunk, contentHash: contentHash });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
matchedIndices.add(chunk.chunkIndex);
|
|
53
|
+
if (existingEntry.contentHash === contentHash) {
|
|
54
|
+
items.push({ verdict: 'equal', chunk: chunk, existingId: existingEntry._id, contentHash: contentHash });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
items.push({ verdict: 'modified', chunk: chunk, existingId: existingEntry._id, contentHash: contentHash });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// (2) A táron maradt, az új batch-ben NEM szereplő kulcsok → deleted (a fájl rövidült/törölt).
|
|
61
|
+
for (const entry of existing) {
|
|
62
|
+
if (typeof entry.chunkIndex === 'number' && !matchedIndices.has(entry.chunkIndex)) {
|
|
63
|
+
items.push({
|
|
64
|
+
verdict: 'deleted',
|
|
65
|
+
chunk: null,
|
|
66
|
+
existingId: entry._id,
|
|
67
|
+
contentHash: entry.contentHash ?? '',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return items;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.FAM_DeltaCompare_Util = FAM_DeltaCompare_Util;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_IngestRun_DataService = void 0;
|
|
4
|
+
const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
|
|
5
|
+
const fam_ingest_run_data_model_1 = require("../../../_models/data-models/fam-ingest-run.data-model");
|
|
6
|
+
/**
|
|
7
|
+
* `FAM_IngestRun_DataService` (SP-4.4, dsgn-001 §7 / dsgn-004 §5) — a `fam_ingest_runs` (MAIN)
|
|
8
|
+
* collection CRUD-ja. `extends DyNTS_DataService` (a `dataParams.addArchive=true` — soft-delete a
|
|
9
|
+
* `getArchiveDataService`-en át, move-to-archive). Minden scan-művelet EGYETLEN run-rekordot ír; a
|
|
10
|
+
* rekord a scan ELEJÉN nyílik (`open`), a VÉGÉN záródik (`close`).
|
|
11
|
+
*
|
|
12
|
+
* **Mongoose Mixed atomic-write (memory: reference_mongoose_mixed_atomic_write):** a `verdicts` /
|
|
13
|
+
* `errors` / `scopePath` Mixed-szerű mezők `findOne→mutate→save`-vel SILENT-DROP-ot adhatnak —
|
|
14
|
+
* ezért a run-FRISSÍTÉS `updateOne({_id},{$set:{...}})` úton megy (`close`), NEM olvasás-mutáció-
|
|
15
|
+
* mentés. A nyitó `open` egy fresh `saveData` (új rekord), ami safe.
|
|
16
|
+
*
|
|
17
|
+
* **FIGYELEM (memory: dynts_dataservice_eager_resolve):** a base-ctor EAGER `getDBService`-t hív,
|
|
18
|
+
* ezért a control-service-ek NEM tartanak élő példányt mezőként — minden művelet előtt lazy `new`.
|
|
19
|
+
*/
|
|
20
|
+
class FAM_IngestRun_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
21
|
+
constructor(set) {
|
|
22
|
+
super(set.data instanceof fam_ingest_run_data_model_1.FAM_IngestRun_DataModel ? set.data : new fam_ingest_run_data_model_1.FAM_IngestRun_DataModel(set.data), fam_ingest_run_data_model_1.famIngestRun_dataParams, set.issuer);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A move-to-archive soft-delete (dsgn-001 §8) archív-collection service-e: a base `deleteData`
|
|
26
|
+
* (addArchive) ezt hívja, hogy a törölt run-rekordot a `fam_ingest_runs_archived` tárba mozgassa. A
|
|
27
|
+
* `DyNTS_DataService` base default-ja DOB (nincs ökoszisztéma-implementáció) — ezért az explicit
|
|
28
|
+
* override (a `DyNTS_ArchiveDataService` a `dataParams.addArchive`-ból oldja az archív-nevet).
|
|
29
|
+
*/
|
|
30
|
+
getArchiveDataService() {
|
|
31
|
+
return new nts_dynamo_1.DyNTS_ArchiveDataService(new fam_ingest_run_data_model_1.FAM_IngestRun_DataModel(), this.dataParams, this.issuer);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A run megnyitása a scan ELEJÉN (dsgn-004 §5). A `_id` = a hívó által generált runId (UUID v4),
|
|
35
|
+
* hogy a chunkok az `ingestRunId`-vel HIVATKOZHASSANAK rá MÉG a run-zárás előtt. Friss `saveData`
|
|
36
|
+
* (új rekord — nincs Mixed-mutáció). A számlálók/verdiktek a `close`-ban frissülnek.
|
|
37
|
+
*/
|
|
38
|
+
async open(set) {
|
|
39
|
+
const record = new fam_ingest_run_data_model_1.FAM_IngestRun_DataModel({
|
|
40
|
+
_id: set.runId,
|
|
41
|
+
trigger: set.trigger,
|
|
42
|
+
table: set.table,
|
|
43
|
+
scopePath: set.scopePath,
|
|
44
|
+
rootPath: set.rootPath,
|
|
45
|
+
filesProcessed: 0,
|
|
46
|
+
filesSkipped: 0,
|
|
47
|
+
chunkCount: 0,
|
|
48
|
+
verdicts: { new: 0, modified: 0, equal: 0, deleted: 0 },
|
|
49
|
+
status: 'ok',
|
|
50
|
+
errors: [],
|
|
51
|
+
});
|
|
52
|
+
return this.saveData(record);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A run lezárása a scan VÉGÉN (dsgn-004 §5) — atomikus `$set` (Mixed silent-drop ellen). A
|
|
56
|
+
* `verdicts` / `errors` / a számlálók / `status` / `durationMs` / opc. `table`-bővítés egy
|
|
57
|
+
* lépésben. NEM olvasás-mutáció-mentés.
|
|
58
|
+
*/
|
|
59
|
+
async close(set) {
|
|
60
|
+
const setBlock = {
|
|
61
|
+
table: set.table,
|
|
62
|
+
filesProcessed: set.filesProcessed,
|
|
63
|
+
filesSkipped: set.filesSkipped,
|
|
64
|
+
chunkCount: set.chunkCount,
|
|
65
|
+
verdicts: set.verdicts,
|
|
66
|
+
durationMs: set.durationMs,
|
|
67
|
+
status: set.status,
|
|
68
|
+
errors: set.errors,
|
|
69
|
+
};
|
|
70
|
+
if (set.embedTokenCost !== undefined) {
|
|
71
|
+
setBlock.embedTokenCost = set.embedTokenCost;
|
|
72
|
+
}
|
|
73
|
+
const update = { $set: setBlock };
|
|
74
|
+
await this.updateData({ filterBy: { _id: set.runId }, update: update });
|
|
75
|
+
}
|
|
76
|
+
/** Egy run a `_id` alapján (vagy üres-objektum, ha nincs). */
|
|
77
|
+
async findRunById(runId) {
|
|
78
|
+
return this.findData({ _id: runId }, true);
|
|
79
|
+
}
|
|
80
|
+
/** A run-ok lapozatlan listája (legutóbbiak felül; a `list_scan_runs` capability, MP-6 fogyasztja). */
|
|
81
|
+
async listRuns(filter) {
|
|
82
|
+
return this.findDataList(filter ?? {}, true);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.FAM_IngestRun_DataService = FAM_IngestRun_DataService;
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Ingest_ControlService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const crypto = tslib_1.__importStar(require("crypto"));
|
|
6
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
+
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
8
|
+
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
9
|
+
const errors_control_service_1 = require("../../../_routes/server/errors/errors.control-service");
|
|
10
|
+
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
11
|
+
const fam_entry_data_model_1 = require("../../../_models/data-models/fam-entry.data-model");
|
|
12
|
+
const embedding_1 = require("../../embedding");
|
|
13
|
+
const scope_reference_1 = require("../../scope-reference");
|
|
14
|
+
const fam_chunker_control_service_1 = require("./fam-chunker.control-service");
|
|
15
|
+
const fam_delta_compare_util_1 = require("./fam-delta-compare.util");
|
|
16
|
+
const fam_ingest_run_data_service_1 = require("./fam-ingest-run.data-service");
|
|
17
|
+
const fam_scan_control_service_1 = require("./fam-scan.control-service");
|
|
18
|
+
/**
|
|
19
|
+
* `FAM_Ingest_ControlService` (SP-4.4, dsgn-004 §1/§4/§5) — az ingest-orchestrátor. A teljes
|
|
20
|
+
* `write(scan-*)` pipeline EGY belépési ponton:
|
|
21
|
+
*
|
|
22
|
+
* `scan*` → scope reference-resolve (MP-3) → fájl-felderítés + routing (SP-4.1) → per-fájl chunk
|
|
23
|
+
* (SP-4.2) → delta-detection (SP-4.3) → embed CSAK a `new`/`modified`-re (MP-2 `embedAndPersist`) →
|
|
24
|
+
* persist (`addedBy='scan'`, `ingestRunId`, canonical `scopePath`) → `deleted` soft-delete →
|
|
25
|
+
* `FAM_IngestRun` összefoglaló + `famIngest` MON-esemény (dsgn-004 §5).
|
|
26
|
+
*
|
|
27
|
+
* **Idempotencia (dsgn-004 §8.4):** ugyanazt a forrást kétszer scan-elve a 2. futás minden chunkja
|
|
28
|
+
* `equal` → 0 `new`/`modified` → 0 embedding-hívás (a `embedAndPersist` CSAK a `new`/`modified`-re fut).
|
|
29
|
+
*
|
|
30
|
+
* **Részleges hiba (dsgn-004 §8.9, no-silent-failure):** egy fájl chunking-/embedding-hibája NEM
|
|
31
|
+
* buktatja az egész run-t — a hiba `errors[]`-be (a központi error-rétegen, MP-8), a `status`
|
|
32
|
+
* `partial-failed`-re emelve, a többi fájl feldolgozva. A scope-resolve hibája viszont a scan ELŐTT
|
|
33
|
+
* bukik (a run `failed`, NINCS fájl-bejárás).
|
|
34
|
+
*
|
|
35
|
+
* Singleton (memory: dynts_dataservice_eager_resolve — NEM tartunk élő DataService-mezőt).
|
|
36
|
+
*/
|
|
37
|
+
class FAM_Ingest_ControlService {
|
|
38
|
+
static _instance;
|
|
39
|
+
/** Default issuer az ingest-műveletekhez. */
|
|
40
|
+
issuer = 'FAM_Ingest_ControlService';
|
|
41
|
+
static getInstance() {
|
|
42
|
+
if (!FAM_Ingest_ControlService._instance) {
|
|
43
|
+
FAM_Ingest_ControlService._instance = new FAM_Ingest_ControlService();
|
|
44
|
+
}
|
|
45
|
+
return FAM_Ingest_ControlService._instance;
|
|
46
|
+
}
|
|
47
|
+
// =========================================================================
|
|
48
|
+
// Belépési pontok (a `write` MCP-eszköz / REST — MP-6 — ezeket hívja)
|
|
49
|
+
// =========================================================================
|
|
50
|
+
/** `scan-file` (dsgn-004 §1) — egyetlen fájl re-ingestje. */
|
|
51
|
+
async scanFile(request) {
|
|
52
|
+
return this.runScan({ ...request, operation: 'scan-file' });
|
|
53
|
+
}
|
|
54
|
+
/** `scan-folder` (dsgn-004 §1) — egy mappa (rekurzív) fájljainak ingestje. */
|
|
55
|
+
async scanFolder(request) {
|
|
56
|
+
return this.runScan({ ...request, operation: 'scan-folder' });
|
|
57
|
+
}
|
|
58
|
+
/** `scan-project` (dsgn-004 §1) — a teljes projekt-korpusz egyetlen művelettel. */
|
|
59
|
+
async scanProject(request) {
|
|
60
|
+
return this.runScan({ ...request, operation: 'scan-project' });
|
|
61
|
+
}
|
|
62
|
+
// =========================================================================
|
|
63
|
+
// Group-by-run + group-delete (SP-4.4, dsgn-004 §5; `list_scan_runs` capability — MP-6)
|
|
64
|
+
// =========================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Egy futtatás összes (nem soft-deleted) chunkja minden fő tárból (dsgn-004 §5 csoport-megjelenítés).
|
|
67
|
+
* A `list_scan_runs` → run-chunks capability (MP-6) ezt fogyasztja. A `FAM_STORE_REGISTRY` minden
|
|
68
|
+
* fő táron lekérdez az `ingestRunId`-re (egy scan-project több tárat is érinthet).
|
|
69
|
+
*/
|
|
70
|
+
async listRunChunks(ingestRunId) {
|
|
71
|
+
const result = [];
|
|
72
|
+
for (const registryEntry of embedding_1.FAM_STORE_REGISTRY) {
|
|
73
|
+
const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
74
|
+
const filter = { ingestRunId: ingestRunId };
|
|
75
|
+
const chunks = await dataService.findHydratableList(filter);
|
|
76
|
+
result.push(...chunks);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Egy futtatás összes chunkjának csoportos SOFT-delete-je minden fő tárból (dsgn-004 §5
|
|
82
|
+
* delete-run capability — MP-6 maintenance). A törölt chunkok NEM retrieve-elődnek (MP-5). A run
|
|
83
|
+
* rekordot maga NEM törli (audit-history megmarad). Visszaadja a törölt chunkok számát.
|
|
84
|
+
*/
|
|
85
|
+
async deleteRun(ingestRunId) {
|
|
86
|
+
let deleted = 0;
|
|
87
|
+
for (const registryEntry of embedding_1.FAM_STORE_REGISTRY) {
|
|
88
|
+
const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
89
|
+
const chunks = await dataService.findHydratableList({ ingestRunId: ingestRunId });
|
|
90
|
+
for (const chunk of chunks) {
|
|
91
|
+
if (chunk._id) {
|
|
92
|
+
await dataService.deleteData(chunk._id);
|
|
93
|
+
deleted++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return deleted;
|
|
98
|
+
}
|
|
99
|
+
// =========================================================================
|
|
100
|
+
// Az ingest-pipeline mag (SP-4.4)
|
|
101
|
+
// =========================================================================
|
|
102
|
+
/**
|
|
103
|
+
* A teljes scan-pipeline (dsgn-004 §1). A scope KÖTELEZŐ reference-resolve-on megy át (MP-3); a
|
|
104
|
+
* resolve-hiba → a run `failed` (NINCS fájl-bejárás). A felderítés + per-fájl feldolgozás után a
|
|
105
|
+
* run lezárul a verdict-aggregátummal + `status`-szal; a MON-esemény kibocsátódik.
|
|
106
|
+
*/
|
|
107
|
+
async runScan(request) {
|
|
108
|
+
const issuer = request.issuer ?? this.issuer;
|
|
109
|
+
const runId = crypto.randomUUID();
|
|
110
|
+
const startedAt = Date.now();
|
|
111
|
+
// (1) Scope reference-resolve (MP-3 `resolveForWrite`) — KÖTELEZŐ. Hiba → run `failed`.
|
|
112
|
+
let canonicalScope;
|
|
113
|
+
try {
|
|
114
|
+
const resolved = await scope_reference_1.FAM_ScopeResolver_ControlService.getInstance().resolveForWrite(this.toRawScope(request.scopePath));
|
|
115
|
+
canonicalScope = resolved.scopePath;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// A scope feloldatlan → a run NEM futott le. A scope-hiba MÁR persistált (MP-3 emitálja);
|
|
119
|
+
// itt csak a `failed` run-summary-t adjuk vissza (a hívó deskriptív blokkot kap).
|
|
120
|
+
return this.failedSummary({
|
|
121
|
+
runId: runId, request: request, startedAt: startedAt,
|
|
122
|
+
error: error, scopePath: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// (2) Fájl-felderítés + routing + security (SP-4.1).
|
|
126
|
+
let discovery;
|
|
127
|
+
try {
|
|
128
|
+
discovery = await fam_scan_control_service_1.FAM_Scan_ControlService.getInstance().discover({
|
|
129
|
+
operation: request.operation,
|
|
130
|
+
path: request.path,
|
|
131
|
+
scopePath: canonicalScope,
|
|
132
|
+
include: request.include,
|
|
133
|
+
exclude: request.exclude,
|
|
134
|
+
tableOverride: request.tableOverride,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return this.failedSummary({
|
|
139
|
+
runId: runId, request: request, startedAt: startedAt,
|
|
140
|
+
error: error, scopePath: canonicalScope,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// (3) A run megnyitása (a chunkok ezzel az `ingestRunId`-vel hivatkoznak rá).
|
|
144
|
+
const runDataService = new fam_ingest_run_data_service_1.FAM_IngestRun_DataService({ issuer: issuer });
|
|
145
|
+
await runDataService.open({
|
|
146
|
+
runId: runId,
|
|
147
|
+
trigger: request.operation,
|
|
148
|
+
table: discovery.tables,
|
|
149
|
+
scopePath: canonicalScope,
|
|
150
|
+
rootPath: request.path,
|
|
151
|
+
});
|
|
152
|
+
// (4) Per-fájl feldolgozás (flow-limit kötegekben; a per-fájl hiba NEM buktatja a run-t).
|
|
153
|
+
const verdicts = { new: 0, modified: 0, equal: 0, deleted: 0 };
|
|
154
|
+
const errors = [];
|
|
155
|
+
let chunkCount = 0;
|
|
156
|
+
const flowLimit = await this.resolveFlowLimit(canonicalScope);
|
|
157
|
+
for (let i = 0; i < discovery.routedFiles.length; i += flowLimit) {
|
|
158
|
+
const batch = discovery.routedFiles.slice(i, i + flowLimit);
|
|
159
|
+
const results = await Promise.all(batch.map((file) => this.processFile({
|
|
160
|
+
file: file, runId: runId, scopePath: canonicalScope,
|
|
161
|
+
dryRun: request.dryRun ?? false, issuer: issuer, errors: errors,
|
|
162
|
+
})));
|
|
163
|
+
for (const result of results) {
|
|
164
|
+
if (!result) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
chunkCount += result.chunkCount;
|
|
168
|
+
this.tallyVerdicts(verdicts, result.deltaItems);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// (5) A run lezárása (atomikus `$set`) + MON-esemény.
|
|
172
|
+
const status = errors.length ? 'partial-failed' : 'ok';
|
|
173
|
+
const durationMs = Date.now() - startedAt;
|
|
174
|
+
await runDataService.close({
|
|
175
|
+
runId: runId, table: discovery.tables,
|
|
176
|
+
filesProcessed: discovery.routedFiles.length, filesSkipped: discovery.skippedFiles.length,
|
|
177
|
+
chunkCount: chunkCount, verdicts: verdicts, durationMs: durationMs, status: status, errors: errors,
|
|
178
|
+
});
|
|
179
|
+
const summary = {
|
|
180
|
+
ingestRunId: runId, trigger: request.operation, table: discovery.tables,
|
|
181
|
+
scopePath: canonicalScope, rootPath: request.path,
|
|
182
|
+
filesProcessed: discovery.routedFiles.length, filesSkipped: discovery.skippedFiles.length,
|
|
183
|
+
chunkCount: chunkCount, verdicts: verdicts, durationMs: durationMs, status: status, errors: errors,
|
|
184
|
+
};
|
|
185
|
+
this.emitMonEvent(summary);
|
|
186
|
+
return summary;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Egy route-olt fájl feldolgozása (dsgn-004 §3/§4): olvasás → chunk (SP-4.2) → delta (SP-4.3) →
|
|
190
|
+
* akció-routing a verdikt szerint (`equal`→skip, `new`/`modified`→embed+persist, `deleted`→
|
|
191
|
+
* soft-delete). A per-fájl hiba `errors[]`-be (no-silent-failure) + `null` visszatérés (a run nem bukik).
|
|
192
|
+
*/
|
|
193
|
+
async processFile(set) {
|
|
194
|
+
const table = set.file.route;
|
|
195
|
+
try {
|
|
196
|
+
const content = fs.readFileSync(set.file.absolutePath, 'utf-8');
|
|
197
|
+
const chunks = await fam_chunker_control_service_1.FAM_Chunker_ControlService.getInstance().chunkFile({
|
|
198
|
+
content: content, relativePath: set.file.relativePath, table: table, scopePath: set.scopePath,
|
|
199
|
+
});
|
|
200
|
+
const registryEntry = embedding_1.FAM_StoreRegistry_Util.getEntry(table);
|
|
201
|
+
if (!registryEntry) {
|
|
202
|
+
throw new Error(`No store-registry entry for table '${table}'.`);
|
|
203
|
+
}
|
|
204
|
+
const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: set.issuer });
|
|
205
|
+
// Existing-by-key betöltés (a fájl nem soft-deleted chunkjai, scope-szűkítve).
|
|
206
|
+
const existing = await this.loadExistingChunks(dataService, set.file.relativePath, set.scopePath);
|
|
207
|
+
const deltaItems = fam_delta_compare_util_1.FAM_DeltaCompare_Util.compare(chunks, existing);
|
|
208
|
+
if (!set.dryRun) {
|
|
209
|
+
await this.applyVerdicts({
|
|
210
|
+
deltaItems: deltaItems, file: set.file, table: table, runId: set.runId,
|
|
211
|
+
scopePath: set.scopePath, dataService: dataService,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
file: set.file,
|
|
216
|
+
deltaItems: deltaItems,
|
|
217
|
+
chunkCount: chunks.length,
|
|
218
|
+
fallbackUsed: chunks.length === 1 && chunks[0].chunkType === 'fallback',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
// No-silent-failure: a per-fájl hibát persistáljuk + az `errors[]`-be tesszük (a run
|
|
223
|
+
// `partial-failed`-re emelkedik, a többi fájl feldolgozva — dsgn-004 §8.9).
|
|
224
|
+
const famError = await errors_control_service_1.FAM_Error_ControlService.getInstance().emit({
|
|
225
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.scanRead,
|
|
226
|
+
message: `A fájl feldolgozása sikertelen a scan során: '${set.file.relativePath}'. A run a többi `
|
|
227
|
+
+ 'fájllal folytatódik (partial-failed).',
|
|
228
|
+
cause: error,
|
|
229
|
+
context: {
|
|
230
|
+
operation: 'ingest-process-file', runId: set.runId,
|
|
231
|
+
sourceFilePath: set.file.relativePath, table: table,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
set.errors.push({
|
|
235
|
+
errorCode: fsm_dynamo_1.DyFM_Error.getErrorCode(famError),
|
|
236
|
+
message: fsm_dynamo_1.DyFM_Error.getErrorMessage(famError),
|
|
237
|
+
sourceFilePath: set.file.relativePath,
|
|
238
|
+
});
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Az akció-routing a delta-verdiktek szerint (dsgn-004 §4.2): `equal` → skip (NINCS re-embed);
|
|
244
|
+
* `new` → insert + embed; `modified` → re-embed + `$set`; `deleted` → soft-delete. Az embed CSAK
|
|
245
|
+
* a `new`/`modified`-re fut (MP-2 `embedAndPersist`) — ez az embedding-költség fő minimalizálója.
|
|
246
|
+
*/
|
|
247
|
+
async applyVerdicts(set) {
|
|
248
|
+
for (const item of set.deltaItems) {
|
|
249
|
+
if (item.verdict === 'equal') {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (item.verdict === 'deleted') {
|
|
253
|
+
if (item.existingId) {
|
|
254
|
+
await set.dataService.deleteData(item.existingId);
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// new / modified → upsert + embed (a `chunk` itt sosem null).
|
|
259
|
+
if (item.chunk) {
|
|
260
|
+
await this.persistAndEmbed({
|
|
261
|
+
chunk: item.chunk, existingId: item.existingId, contentHash: item.contentHash,
|
|
262
|
+
file: set.file, table: set.table, runId: set.runId, scopePath: set.scopePath,
|
|
263
|
+
dataService: set.dataService,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Egy `new`/`modified` chunk persistálása + embeddelése (dsgn-004 §4.2 + dsgn-006 §3). A
|
|
270
|
+
* `FAM_Entry` a chunk-mezőkkel + scope + `ingestRunId` + `addedBy='scan'` + `source` provenance;
|
|
271
|
+
* a mentés UTÁN a MP-2 `embedAndPersist` vektorizál (CSAK itt — az `equal`-eken NEM).
|
|
272
|
+
*/
|
|
273
|
+
async persistAndEmbed(set) {
|
|
274
|
+
const entry = new fam_entry_data_model_1.FAM_Entry({
|
|
275
|
+
_id: set.existingId,
|
|
276
|
+
table: set.table,
|
|
277
|
+
content: set.chunk.content,
|
|
278
|
+
contentHash: set.contentHash,
|
|
279
|
+
embeddingStatus: 'pending',
|
|
280
|
+
scopePath: set.scopePath,
|
|
281
|
+
sourceFilePath: set.file.relativePath,
|
|
282
|
+
chunkIndex: set.chunk.chunkIndex,
|
|
283
|
+
chunkTotal: set.chunk.chunkTotal,
|
|
284
|
+
chunkType: set.chunk.chunkType,
|
|
285
|
+
position: set.chunk.position,
|
|
286
|
+
headingPath: set.chunk.headingPath,
|
|
287
|
+
source: { type: 'scan', path: set.file.relativePath },
|
|
288
|
+
addedBy: 'scan',
|
|
289
|
+
ingestRunId: set.runId,
|
|
290
|
+
});
|
|
291
|
+
// Persist (saveData = upsert: meglévő `_id` → modify, hiányzó → insert).
|
|
292
|
+
const saved = await set.dataService.saveData(entry);
|
|
293
|
+
// Embed CSAK a new/modified-re (MP-2). Az embed-hiba az entry-t `error`-státuszba teszi +
|
|
294
|
+
// persistál (a `embedAndPersist` belül kezeli, no-silent-failure) — NEM dob ide.
|
|
295
|
+
await embedding_1.FAM_EmbeddingPipeline_ControlService.getInstance().embedAndPersist({
|
|
296
|
+
table: set.table, entry: saved, callType: 'embed-write',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// =========================================================================
|
|
300
|
+
// helpers
|
|
301
|
+
// =========================================================================
|
|
302
|
+
/**
|
|
303
|
+
* A fájl meglévő (nem soft-deleted) chunkjai a táron + scope-on belül (`Map`-elésre a delta-
|
|
304
|
+
* kulcshoz, SP-4.3). A `findHydratableList` az aktívakat adja (a soft-delete-elteket kihagyja).
|
|
305
|
+
* A scope-szűkítés a LEVÉL scopeId-jára (a write-path canonical scopePath-ot ír az entry-re).
|
|
306
|
+
*/
|
|
307
|
+
async loadExistingChunks(dataService, sourceFilePath, scopePath) {
|
|
308
|
+
const filter = { sourceFilePath: sourceFilePath };
|
|
309
|
+
if (scopePath.length) {
|
|
310
|
+
const leaf = scopePath[scopePath.length - 1];
|
|
311
|
+
filter['scopePath.scopeId'] = leaf.scopeId;
|
|
312
|
+
}
|
|
313
|
+
return dataService.findHydratableList(filter);
|
|
314
|
+
}
|
|
315
|
+
/** A verdikt-számok aggregálása a `FAM_IngestRun.verdicts`-be. */
|
|
316
|
+
tallyVerdicts(verdicts, items) {
|
|
317
|
+
for (const item of items) {
|
|
318
|
+
verdicts[item.verdict]++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** A nyers `FAM_RawScopeLayer[]` a request scope-jából (a MP-3 `resolveForWrite` bemenete). */
|
|
322
|
+
toRawScope(scopePath) {
|
|
323
|
+
return scopePath;
|
|
324
|
+
}
|
|
325
|
+
/** A párhuzamos fájl-feldolgozás flow-limitje a config-ból (`ingest.flowLimit`, default 20, cap 200). */
|
|
326
|
+
async resolveFlowLimit(scopePath) {
|
|
327
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('ingest.flowLimit', { scopePath: scopePath });
|
|
328
|
+
const value = typeof resolved.value === 'number' ? resolved.value : 20;
|
|
329
|
+
return Math.min(Math.max(value, 1), 200);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* A `failed` run-summary (a scope-resolve / felderítés bukásakor). A run rekord-mentés best-effort
|
|
333
|
+
* (a hiba MÁR persistált); a hívónak a `failed` summary jelzi, hogy a scan nem futott le.
|
|
334
|
+
*/
|
|
335
|
+
async failedSummary(set) {
|
|
336
|
+
const errorEntry = { message: set.error?.message ?? 'scan failed before file walk' };
|
|
337
|
+
const summary = {
|
|
338
|
+
ingestRunId: set.runId, trigger: set.request.operation, table: [],
|
|
339
|
+
scopePath: set.scopePath, rootPath: set.request.path,
|
|
340
|
+
filesProcessed: 0, filesSkipped: 0, chunkCount: 0,
|
|
341
|
+
verdicts: { new: 0, modified: 0, equal: 0, deleted: 0 },
|
|
342
|
+
durationMs: Date.now() - set.startedAt, status: 'failed', errors: [errorEntry],
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
const runDataService = new fam_ingest_run_data_service_1.FAM_IngestRun_DataService({ issuer: this.issuer });
|
|
346
|
+
await runDataService.open({
|
|
347
|
+
runId: set.runId, trigger: set.request.operation, table: [],
|
|
348
|
+
scopePath: set.scopePath, rootPath: set.request.path,
|
|
349
|
+
});
|
|
350
|
+
await runDataService.close({
|
|
351
|
+
runId: set.runId, table: [], filesProcessed: 0, filesSkipped: 0, chunkCount: 0,
|
|
352
|
+
verdicts: summary.verdicts, durationMs: summary.durationMs, status: 'failed', errors: summary.errors,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (persistError) {
|
|
356
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM ingest] a failed-run rekord mentése sikertelen (runId=${set.runId}): `
|
|
357
|
+
+ `${persistError?.message}`);
|
|
358
|
+
}
|
|
359
|
+
this.emitMonEvent(summary);
|
|
360
|
+
return summary;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* A `famIngest` MON-esemény a run végén (dsgn-004 §5; a CCAP `ragIngest` általánosítva). MVP1: a
|
|
364
|
+
* `DyFM_Log.info`-n strukturált payload-dal (a MP-8/MP-10 Logs-modul + idővonal-wiring KÉSŐBBI MP;
|
|
365
|
+
* a Logs-routing még nincs installálva az App-ban — ezt NEM rántjuk előre). A payload shape a
|
|
366
|
+
* dsgn-004 §5 szerinti (event/ingestRunId/trigger/table/verdicts/durationMs/status).
|
|
367
|
+
*/
|
|
368
|
+
emitMonEvent(summary) {
|
|
369
|
+
fsm_dynamo_1.DyFM_Log.info('[famIngest]', {
|
|
370
|
+
event: 'famIngest',
|
|
371
|
+
ingestRunId: summary.ingestRunId,
|
|
372
|
+
trigger: summary.trigger,
|
|
373
|
+
table: summary.table,
|
|
374
|
+
scopePath: summary.scopePath.map((ref) => ref.canonicalName).join('>'),
|
|
375
|
+
filesProcessed: summary.filesProcessed,
|
|
376
|
+
filesSkipped: summary.filesSkipped,
|
|
377
|
+
chunkCount: summary.chunkCount,
|
|
378
|
+
verdicts: summary.verdicts,
|
|
379
|
+
durationMs: summary.durationMs,
|
|
380
|
+
status: summary.status,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
exports.FAM_Ingest_ControlService = FAM_Ingest_ControlService;
|