@hamak/smart-data-dico 1.19.0 → 1.22.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/backend/dist/server.mjs +857 -528
- package/frontend/dist/assets/{abnfDiagram-VRR7QNED-5bf36645.js → abnfDiagram-VRR7QNED-6f198720.js} +1 -1
- package/frontend/dist/assets/{arc-77b99720.js → arc-54f6dc83.js} +1 -1
- package/frontend/dist/assets/architecture-TIHT7OUA-8222218c.js +1 -0
- package/frontend/dist/assets/{architectureDiagram-ZJ3FMSHR-842d4b7e.js → architectureDiagram-ZJ3FMSHR-6dabc0f0.js} +1 -1
- package/frontend/dist/assets/{blockDiagram-677ZJIJ3-ce2d6065.js → blockDiagram-677ZJIJ3-90ab5147.js} +1 -1
- package/frontend/dist/assets/{c4Diagram-LMCZKHZV-f6f33075.js → c4Diagram-LMCZKHZV-b5a971e3.js} +1 -1
- package/frontend/dist/assets/channel-98106291.js +1 -0
- package/frontend/dist/assets/{chunk-2Q5K7J3B-e048f182.js → chunk-2Q5K7J3B-ac60a5fb.js} +1 -1
- package/frontend/dist/assets/{chunk-32BRIVSS-9f8a0696.js → chunk-32BRIVSS-ddb07474.js} +1 -1
- package/frontend/dist/assets/{chunk-5VM5RSS4-8207b825.js → chunk-5VM5RSS4-2db6a06c.js} +1 -1
- package/frontend/dist/assets/{chunk-EX3LRPZG-4964b359.js → chunk-EX3LRPZG-baa20613.js} +1 -1
- package/frontend/dist/assets/{chunk-JWPE2WC7-8cf4213d.js → chunk-JWPE2WC7-18647660.js} +1 -1
- package/frontend/dist/assets/{chunk-MOJQB5TN-7c50afe9.js → chunk-MOJQB5TN-dc09b962.js} +1 -1
- package/frontend/dist/assets/{chunk-PUDLZKDR-49760488.js → chunk-PUDLZKDR-6287f125.js} +1 -1
- package/frontend/dist/assets/{chunk-RYQCIY6F-0690c709.js → chunk-RYQCIY6F-9786102e.js} +1 -1
- package/frontend/dist/assets/{chunk-V7JOEXUC-4c92f24d.js → chunk-V7JOEXUC-158db19a.js} +1 -1
- package/frontend/dist/assets/{chunk-VR4S4FIN-73477ca7.js → chunk-VR4S4FIN-8a5a7523.js} +1 -1
- package/frontend/dist/assets/{chunk-XXDRQBXY-9faa344e.js → chunk-XXDRQBXY-f0aeb3a2.js} +1 -1
- package/frontend/dist/assets/classDiagram-OUVF2IWQ-6fddb962.js +1 -0
- package/frontend/dist/assets/classDiagram-v2-EOCWNBFH-6fddb962.js +1 -0
- package/frontend/dist/assets/{cose-bilkent-JH36ORCC-eca50abe.js → cose-bilkent-JH36ORCC-97aedf6d.js} +1 -1
- package/frontend/dist/assets/cynefin-VYW2F7L2-39a09b7c.js +1 -0
- package/frontend/dist/assets/{cynefinDiagram-TSTJHNR4-9daa8c60.js → cynefinDiagram-TSTJHNR4-7414b003.js} +1 -1
- package/frontend/dist/assets/{dagre-VKFMJZFB-8cadf293.js → dagre-VKFMJZFB-22f0ea2d.js} +1 -1
- package/frontend/dist/assets/{diagram-FQU43EPY-5e4d3a2a.js → diagram-FQU43EPY-54e670d8.js} +1 -1
- package/frontend/dist/assets/{diagram-G47NLZAW-07630f46.js → diagram-G47NLZAW-41216f63.js} +1 -1
- package/frontend/dist/assets/{diagram-NH7WQ7WH-58c6af5e.js → diagram-NH7WQ7WH-2de1d465.js} +1 -1
- package/frontend/dist/assets/{diagram-OA4YK3LP-b0124115.js → diagram-OA4YK3LP-4be5e2c2.js} +1 -1
- package/frontend/dist/assets/{diagram-WEI45ONY-f942b2f7.js → diagram-WEI45ONY-0d1d5dd3.js} +1 -1
- package/frontend/dist/assets/{ebnfDiagram-CCIWWBDH-42fc377c.js → ebnfDiagram-CCIWWBDH-235afe69.js} +1 -1
- package/frontend/dist/assets/{erDiagram-Q63AITRT-46209ae5.js → erDiagram-Q63AITRT-bfbc0736.js} +1 -1
- package/frontend/dist/assets/eventmodeling-45OFAUF4-c1d13b17.js +1 -0
- package/frontend/dist/assets/flowDiagram-23GEKE2U-50c416a3.js +1 -0
- package/frontend/dist/assets/{ganttDiagram-NO4QXBWP-e368e881.js → ganttDiagram-NO4QXBWP-d767d5bd.js} +1 -1
- package/frontend/dist/assets/gitGraph-TEB2WS4Q-53567b0e.js +1 -0
- package/frontend/dist/assets/{gitGraphDiagram-IHSO6WYX-245b8d31.js → gitGraphDiagram-IHSO6WYX-0af7483d.js} +1 -1
- package/frontend/dist/assets/{index-419b7a82.css → index-017e890a.css} +1 -1
- package/frontend/dist/assets/index-25c4649a.js +574 -0
- package/frontend/dist/assets/info-DKCQHKI2-dfc9537f.js +1 -0
- package/frontend/dist/assets/{infoDiagram-FWYZ7A6U-af51f32a.js → infoDiagram-FWYZ7A6U-703eef1c.js} +1 -1
- package/frontend/dist/assets/{ishikawaDiagram-FXEZZL3T-9f2be8c6.js → ishikawaDiagram-FXEZZL3T-b6e22c6c.js} +1 -1
- package/frontend/dist/assets/{journeyDiagram-5HDEW3XC-1e5b0d35.js → journeyDiagram-5HDEW3XC-7ff9040e.js} +1 -1
- package/frontend/dist/assets/{kanban-definition-HUTT4EX6-89d91f2e.js → kanban-definition-HUTT4EX6-3ec9df2c.js} +1 -1
- package/frontend/dist/assets/{linear-5deaa3d1.js → linear-a840de21.js} +1 -1
- package/frontend/dist/assets/{mermaid-parser.core-a7dd4921.js → mermaid-parser.core-35fa1177.js} +2 -2
- package/frontend/dist/assets/{mermaid.core-d2196c74.js → mermaid.core-33299d26.js} +3 -3
- package/frontend/dist/assets/{mindmap-definition-LN4V7U3C-36c63353.js → mindmap-definition-LN4V7U3C-29c921b2.js} +1 -1
- package/frontend/dist/assets/packet-7NZHBO7P-e3add55d.js +1 -0
- package/frontend/dist/assets/{pegDiagram-2B236MQR-30a8ce0f.js → pegDiagram-2B236MQR-c9fd64ea.js} +1 -1
- package/frontend/dist/assets/pie-RZYD4A2V-3cdcefc3.js +1 -0
- package/frontend/dist/assets/{pieDiagram-ENE6RG2P-639c88ff.js → pieDiagram-ENE6RG2P-d21bc176.js} +1 -1
- package/frontend/dist/assets/{quadrantDiagram-ABIIQ3AL-6ff9cdb3.js → quadrantDiagram-ABIIQ3AL-b4dc829d.js} +1 -1
- package/frontend/dist/assets/radar-I7S5WNFK-29afeb7a.js +1 -0
- package/frontend/dist/assets/railroad-3IZDKUUU-6e6a0bb5.js +1 -0
- package/frontend/dist/assets/railroad-abnf-AHOZXSZD-8e00f421.js +1 -0
- package/frontend/dist/assets/railroad-ebnf-EBAXGLYW-89a4f0dc.js +1 -0
- package/frontend/dist/assets/railroad-peg-LSFZ7HO6-b2782816.js +1 -0
- package/frontend/dist/assets/{railroadDiagram-RFXS5EU6-ac789252.js → railroadDiagram-RFXS5EU6-404ec27d.js} +1 -1
- package/frontend/dist/assets/{requirementDiagram-TGXJPOKE-0ce87e1d.js → requirementDiagram-TGXJPOKE-9b582bdd.js} +1 -1
- package/frontend/dist/assets/{sankeyDiagram-HTMAVEWB-4a0abb39.js → sankeyDiagram-HTMAVEWB-f6e0379c.js} +1 -1
- package/frontend/dist/assets/{sequenceDiagram-DBY2YBRQ-284dc47a.js → sequenceDiagram-DBY2YBRQ-508afe00.js} +1 -1
- package/frontend/dist/assets/{sizeCapture-X5ZJPWSS-253d1130.js → sizeCapture-X5ZJPWSS-ee602857.js} +1 -1
- package/frontend/dist/assets/{stateDiagram-2N3HPSRC-5baf911d.js → stateDiagram-2N3HPSRC-1e66e9f0.js} +1 -1
- package/frontend/dist/assets/stateDiagram-v2-6OUMAXLB-660e6df7.js +1 -0
- package/frontend/dist/assets/{swimlanes-5IMT3BWC-1587bceb.js → swimlanes-5IMT3BWC-5fa2813b.js} +1 -1
- package/frontend/dist/assets/swimlanesDiagram-G3AALYLV-f402ef76.js +8 -0
- package/frontend/dist/assets/{timeline-definition-FHXFAJF6-644a682a.js → timeline-definition-FHXFAJF6-12edf3b3.js} +1 -1
- package/frontend/dist/assets/treeView-QDETBFTQ-144f174a.js +1 -0
- package/frontend/dist/assets/treemap-6X3UGDF4-1c06331f.js +1 -0
- package/frontend/dist/assets/{vennDiagram-L72KCM5P-27901c69.js → vennDiagram-L72KCM5P-6f3acb6a.js} +1 -1
- package/frontend/dist/assets/wardley-OPB4EBWU-abf74bee.js +1 -0
- package/frontend/dist/assets/{wardleyDiagram-EHGQE667-7816c0a5.js → wardleyDiagram-EHGQE667-bf6c6922.js} +1 -1
- package/frontend/dist/assets/{xychartDiagram-FW5EYKEG-a3ce4246.js → xychartDiagram-FW5EYKEG-3f6dfba6.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/architecture-TIHT7OUA-a639eab7.js +0 -1
- package/frontend/dist/assets/channel-58a90d4a.js +0 -1
- package/frontend/dist/assets/classDiagram-OUVF2IWQ-2985892f.js +0 -1
- package/frontend/dist/assets/classDiagram-v2-EOCWNBFH-2985892f.js +0 -1
- package/frontend/dist/assets/cynefin-VYW2F7L2-68d8e42f.js +0 -1
- package/frontend/dist/assets/eventmodeling-45OFAUF4-ca978038.js +0 -1
- package/frontend/dist/assets/flowDiagram-23GEKE2U-e940d995.js +0 -1
- package/frontend/dist/assets/gitGraph-TEB2WS4Q-8176fbb9.js +0 -1
- package/frontend/dist/assets/index-647c7909.js +0 -559
- package/frontend/dist/assets/info-DKCQHKI2-fa612c43.js +0 -1
- package/frontend/dist/assets/packet-7NZHBO7P-069cb37a.js +0 -1
- package/frontend/dist/assets/pie-RZYD4A2V-58f1df35.js +0 -1
- package/frontend/dist/assets/radar-I7S5WNFK-72e8d492.js +0 -1
- package/frontend/dist/assets/railroad-3IZDKUUU-c75f2799.js +0 -1
- package/frontend/dist/assets/railroad-abnf-AHOZXSZD-c58af3ba.js +0 -1
- package/frontend/dist/assets/railroad-ebnf-EBAXGLYW-99be0db3.js +0 -1
- package/frontend/dist/assets/railroad-peg-LSFZ7HO6-e22021de.js +0 -1
- package/frontend/dist/assets/stateDiagram-v2-6OUMAXLB-f84f63b8.js +0 -1
- package/frontend/dist/assets/swimlanesDiagram-G3AALYLV-b6a155f6.js +0 -8
- package/frontend/dist/assets/treeView-QDETBFTQ-d9f2bb66.js +0 -1
- package/frontend/dist/assets/treemap-6X3UGDF4-3377c08d.js +0 -1
- package/frontend/dist/assets/wardley-OPB4EBWU-05517e1a.js +0 -1
package/backend/dist/server.mjs
CHANGED
|
@@ -26807,9 +26807,9 @@ var require_range2 = __commonJS({
|
|
|
26807
26807
|
parseRange(range) {
|
|
26808
26808
|
const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
|
|
26809
26809
|
const memoKey = memoOpts + ":" + range;
|
|
26810
|
-
const
|
|
26811
|
-
if (
|
|
26812
|
-
return
|
|
26810
|
+
const cached3 = cache2.get(memoKey);
|
|
26811
|
+
if (cached3) {
|
|
26812
|
+
return cached3;
|
|
26813
26813
|
}
|
|
26814
26814
|
const loose = this.options.loose;
|
|
26815
26815
|
const hr = loose ? re2[t.HYPHENRANGELOOSE] : re2[t.HYPHENRANGE];
|
|
@@ -37683,7 +37683,8 @@ function normalizeRelationship(rel) {
|
|
|
37683
37683
|
}
|
|
37684
37684
|
function fillAttributeDefaults(entity) {
|
|
37685
37685
|
const fix = /* @__PURE__ */ __name((attrs) => {
|
|
37686
|
-
|
|
37686
|
+
if (!Array.isArray(attrs)) return;
|
|
37687
|
+
for (const a of attrs) {
|
|
37687
37688
|
if (a.required === void 0) a.required = false;
|
|
37688
37689
|
if (a.description === void 0) a.description = "";
|
|
37689
37690
|
if (a.properties) fix(a.properties);
|
|
@@ -44015,10 +44016,10 @@ var init_git_file_info_enricher = __esm({
|
|
|
44015
44016
|
* Get cached status or fetch new one
|
|
44016
44017
|
*/
|
|
44017
44018
|
async getCachedStatus(cacheKey, workspaceId, relativePath) {
|
|
44018
|
-
const
|
|
44019
|
+
const cached3 = this.statusCache.get(cacheKey);
|
|
44019
44020
|
const now2 = Date.now();
|
|
44020
|
-
if (
|
|
44021
|
-
return
|
|
44021
|
+
if (cached3 && now2 - cached3.timestamp < this.cacheTimeout) {
|
|
44022
|
+
return cached3.status;
|
|
44022
44023
|
}
|
|
44023
44024
|
try {
|
|
44024
44025
|
const status = await this.gitService.getStatus(workspaceId, relativePath);
|
|
@@ -44603,8 +44604,8 @@ function invalidatePackageCache(packageName) {
|
|
|
44603
44604
|
else _packageCache.delete(packageName);
|
|
44604
44605
|
}
|
|
44605
44606
|
async function loadPackage(packageName) {
|
|
44606
|
-
const
|
|
44607
|
-
if (
|
|
44607
|
+
const cached3 = _packageCache.get(packageName);
|
|
44608
|
+
if (cached3 && cached3.expires > Date.now()) return cached3.model;
|
|
44608
44609
|
const files = await listPackageYamlFilePaths(packageName);
|
|
44609
44610
|
const parsed = await Promise.all(
|
|
44610
44611
|
files.map(async (p) => ({
|
|
@@ -45760,7 +45761,7 @@ var init_dictionaryService = __esm({
|
|
|
45760
45761
|
let relationships = [];
|
|
45761
45762
|
let cases = [];
|
|
45762
45763
|
try {
|
|
45763
|
-
const model = await loadPackage(
|
|
45764
|
+
const model = await loadPackage(String(dirPath));
|
|
45764
45765
|
entities = model.entities;
|
|
45765
45766
|
relationships = model.relationships;
|
|
45766
45767
|
cases = model.cases.map((c) => ({
|
|
@@ -48507,9 +48508,25 @@ var init_serviceService = __esm({
|
|
|
48507
48508
|
* user to disambiguate.
|
|
48508
48509
|
*/
|
|
48509
48510
|
async findEntityAcrossPackages(entityName, preferredPackage) {
|
|
48511
|
+
const matches = await this.findEntityMatches(entityName, preferredPackage);
|
|
48512
|
+
if (matches.length === 0) return null;
|
|
48513
|
+
if (matches.length === 1) return matches[0];
|
|
48514
|
+
const where = matches.map((m) => m.packageName).join(", ");
|
|
48515
|
+
throw new Error(
|
|
48516
|
+
`Entity "${entityName}" exists in multiple packages: ${where}. Specify the package explicitly.`
|
|
48517
|
+
);
|
|
48518
|
+
}
|
|
48519
|
+
/**
|
|
48520
|
+
* All `{ entity, packageName }` matches for a name. A hit in
|
|
48521
|
+
* `preferredPackage` wins outright (single match, no wider scan) —
|
|
48522
|
+
* same precedence findEntityAcrossPackages always had. Callers that
|
|
48523
|
+
* want a disambiguation list instead of a thrown ambiguity error
|
|
48524
|
+
* (e.g. the AI getEntityDetails tool) consume this directly.
|
|
48525
|
+
*/
|
|
48526
|
+
async findEntityMatches(entityName, preferredPackage) {
|
|
48510
48527
|
if (preferredPackage) {
|
|
48511
48528
|
const e = await this.getEntitySchema(preferredPackage, entityName);
|
|
48512
|
-
if (e) return { entity: e, packageName: preferredPackage };
|
|
48529
|
+
if (e) return [{ entity: e, packageName: preferredPackage }];
|
|
48513
48530
|
}
|
|
48514
48531
|
const allPackages = await listMicroservices();
|
|
48515
48532
|
const matches = [];
|
|
@@ -48518,12 +48535,7 @@ var init_serviceService = __esm({
|
|
|
48518
48535
|
const e = await this.getEntitySchema(p, entityName);
|
|
48519
48536
|
if (e) matches.push({ entity: e, packageName: p });
|
|
48520
48537
|
}
|
|
48521
|
-
|
|
48522
|
-
if (matches.length === 1) return matches[0];
|
|
48523
|
-
const where = matches.map((m) => m.packageName).join(", ");
|
|
48524
|
-
throw new Error(
|
|
48525
|
-
`Entity "${entityName}" exists in multiple packages: ${where}. Specify the package explicitly.`
|
|
48526
|
-
);
|
|
48538
|
+
return matches;
|
|
48527
48539
|
}
|
|
48528
48540
|
async createEntity(service, entity) {
|
|
48529
48541
|
logger.info(`Creating entity: ${service}.${entity.name}`);
|
|
@@ -48645,6 +48657,51 @@ var init_serviceService = __esm({
|
|
|
48645
48657
|
};
|
|
48646
48658
|
}
|
|
48647
48659
|
}
|
|
48660
|
+
/**
|
|
48661
|
+
* Move an entity from one package to another (#move-entity). The entity keeps
|
|
48662
|
+
* its UUID, so every UUID-based reference (relationships — cross-package is
|
|
48663
|
+
* first-class — cases, diagrams, perspectives) keeps resolving; nothing is
|
|
48664
|
+
* orphaned. We write the entity into the target package, then remove it from
|
|
48665
|
+
* the source via the projection directly — this bypasses deleteEntity's
|
|
48666
|
+
* same-package relationship guard, which is meant for destruction, not a
|
|
48667
|
+
* relocation where the references remain valid.
|
|
48668
|
+
*/
|
|
48669
|
+
async moveEntity(sourcePackage, entityName, targetPackage) {
|
|
48670
|
+
logger.info(`Moving entity: ${sourcePackage}.${entityName} -> ${targetPackage}`);
|
|
48671
|
+
if (!targetPackage || targetPackage === sourcePackage) {
|
|
48672
|
+
return { success: false, errors: ["Target package must differ from the source package"] };
|
|
48673
|
+
}
|
|
48674
|
+
try {
|
|
48675
|
+
const entity = await readEntityFile(sourcePackage, entityName);
|
|
48676
|
+
if (!entity) {
|
|
48677
|
+
return { success: false, errors: [`Entity ${sourcePackage}.${entityName} not found`] };
|
|
48678
|
+
}
|
|
48679
|
+
const packages = await listMicroservices();
|
|
48680
|
+
if (!packages.includes(targetPackage)) {
|
|
48681
|
+
return { success: false, errors: [`Target package '${targetPackage}' does not exist`] };
|
|
48682
|
+
}
|
|
48683
|
+
const existingInTarget = await readEntityFile(targetPackage, entityName);
|
|
48684
|
+
if (existingInTarget) {
|
|
48685
|
+
return {
|
|
48686
|
+
success: false,
|
|
48687
|
+
errors: [`Target package '${targetPackage}' already has an entity named '${entityName}'`]
|
|
48688
|
+
};
|
|
48689
|
+
}
|
|
48690
|
+
const projection = getProjection(wsId("dictionaries"));
|
|
48691
|
+
entity.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
48692
|
+
await projection.writeEntity(`packages/${targetPackage}/entities/${entity.name}`, entity);
|
|
48693
|
+
const removed = await projection.deleteEntity(`packages/${sourcePackage}/entities/${entity.name}`);
|
|
48694
|
+
if (!removed) {
|
|
48695
|
+
await projection.deleteEntity(`packages/${targetPackage}/entities/${entity.name}`).catch(() => {
|
|
48696
|
+
});
|
|
48697
|
+
return { success: false, errors: ["Failed to remove the entity from the source package"] };
|
|
48698
|
+
}
|
|
48699
|
+
return { success: true, errors: [] };
|
|
48700
|
+
} catch (error48) {
|
|
48701
|
+
logger.error(`Error moving entity: ${error48}`);
|
|
48702
|
+
return { success: false, errors: [`Error moving entity: ${error48}`] };
|
|
48703
|
+
}
|
|
48704
|
+
}
|
|
48648
48705
|
// --- Relationship CRUD ---
|
|
48649
48706
|
async getPackageRelationships(packageName) {
|
|
48650
48707
|
const packagePath = getPackagePath(packageName);
|
|
@@ -49107,424 +49164,6 @@ var init_serviceService = __esm({
|
|
|
49107
49164
|
}
|
|
49108
49165
|
});
|
|
49109
49166
|
|
|
49110
|
-
// backend/src/models/Rule.ts
|
|
49111
|
-
function validateRule(rule) {
|
|
49112
|
-
const errors = [];
|
|
49113
|
-
if (!rule.name || typeof rule.name !== "string") {
|
|
49114
|
-
errors.push("Rule name is required");
|
|
49115
|
-
} else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rule.name)) {
|
|
49116
|
-
errors.push("Rule name must be kebab-case (lowercase letters, numbers, hyphens)");
|
|
49117
|
-
}
|
|
49118
|
-
if (!rule.description || typeof rule.description !== "string") {
|
|
49119
|
-
errors.push("Rule description is required");
|
|
49120
|
-
}
|
|
49121
|
-
if (!rule.severity || !["info", "warning", "error"].includes(rule.severity)) {
|
|
49122
|
-
errors.push("Rule severity must be one of: info, warning, error");
|
|
49123
|
-
}
|
|
49124
|
-
if (!rule.enforcement || !["save", "process", "advisory"].includes(rule.enforcement)) {
|
|
49125
|
-
errors.push("Rule enforcement must be one of: save, process, advisory");
|
|
49126
|
-
}
|
|
49127
|
-
if (!rule.scope || !["entity", "package", "case", "global"].includes(rule.scope)) {
|
|
49128
|
-
errors.push("Rule scope must be one of: entity, package, case, global");
|
|
49129
|
-
}
|
|
49130
|
-
if (!Array.isArray(rule.targets) || rule.targets.length === 0) {
|
|
49131
|
-
errors.push("Rule must have at least one target");
|
|
49132
|
-
}
|
|
49133
|
-
if (rule.scope === "entity" && !rule.entityUuid) {
|
|
49134
|
-
errors.push("Entity-scoped rules must specify entityUuid");
|
|
49135
|
-
}
|
|
49136
|
-
if (rule.scope === "package" && !rule.packageName) {
|
|
49137
|
-
errors.push("Package-scoped rules must specify packageName");
|
|
49138
|
-
}
|
|
49139
|
-
if (rule.scope === "case" && !rule.caseUuid) {
|
|
49140
|
-
errors.push("Case-scoped rules must specify caseUuid");
|
|
49141
|
-
}
|
|
49142
|
-
if (rule.enforcement === "process") {
|
|
49143
|
-
const stageField = (rule.metadata || []).find((m) => m.name === "process-stage-field");
|
|
49144
|
-
if (!stageField || !stageField.value) {
|
|
49145
|
-
errors.push("Process-enforcement rules must reference a metadata field via process-stage-field");
|
|
49146
|
-
}
|
|
49147
|
-
}
|
|
49148
|
-
return errors;
|
|
49149
|
-
}
|
|
49150
|
-
var init_Rule = __esm({
|
|
49151
|
-
"backend/src/models/Rule.ts"() {
|
|
49152
|
-
"use strict";
|
|
49153
|
-
__name(validateRule, "validateRule");
|
|
49154
|
-
}
|
|
49155
|
-
});
|
|
49156
|
-
|
|
49157
|
-
// backend/src/services/ruleService.ts
|
|
49158
|
-
var ruleService_exports = {};
|
|
49159
|
-
__export(ruleService_exports, {
|
|
49160
|
-
ruleService: () => ruleService
|
|
49161
|
-
});
|
|
49162
|
-
var RuleService, ruleService;
|
|
49163
|
-
var init_ruleService = __esm({
|
|
49164
|
-
"backend/src/services/ruleService.ts"() {
|
|
49165
|
-
"use strict";
|
|
49166
|
-
init_Rule();
|
|
49167
|
-
init_fileOperations();
|
|
49168
|
-
init_uuid();
|
|
49169
|
-
init_logger();
|
|
49170
|
-
init_ProjectionRegistry();
|
|
49171
|
-
init_types();
|
|
49172
|
-
RuleService = class {
|
|
49173
|
-
static {
|
|
49174
|
-
__name(this, "RuleService");
|
|
49175
|
-
}
|
|
49176
|
-
/** List all rules from all storage scopes, optionally filtered. */
|
|
49177
|
-
async listRules(filters = {}) {
|
|
49178
|
-
const all = [];
|
|
49179
|
-
if (!filters.scope || filters.scope === "entity") {
|
|
49180
|
-
const files = await listAllEntityRuleFiles();
|
|
49181
|
-
for (const { service, entityUuid } of files) {
|
|
49182
|
-
const rules = await readEntityRules(service, entityUuid);
|
|
49183
|
-
all.push(...rules);
|
|
49184
|
-
}
|
|
49185
|
-
}
|
|
49186
|
-
if (!filters.scope || filters.scope === "package") {
|
|
49187
|
-
const packages = filters.packageName ? [filters.packageName] : await listPackagesWithRules();
|
|
49188
|
-
for (const pkg of packages) {
|
|
49189
|
-
const rules = await readPackageRules(pkg);
|
|
49190
|
-
all.push(...rules);
|
|
49191
|
-
}
|
|
49192
|
-
}
|
|
49193
|
-
if (!filters.scope || filters.scope === "global") {
|
|
49194
|
-
const globalRules = await readGlobalRules();
|
|
49195
|
-
all.push(...globalRules);
|
|
49196
|
-
}
|
|
49197
|
-
if (!filters.scope || filters.scope === "case") {
|
|
49198
|
-
if (filters.caseUuid) {
|
|
49199
|
-
const rules = await readCaseRules(filters.caseUuid);
|
|
49200
|
-
all.push(...rules);
|
|
49201
|
-
} else {
|
|
49202
|
-
const cases = await listCases();
|
|
49203
|
-
for (const c of cases) {
|
|
49204
|
-
const rules = c.rules || [];
|
|
49205
|
-
all.push(...rules);
|
|
49206
|
-
}
|
|
49207
|
-
}
|
|
49208
|
-
}
|
|
49209
|
-
for (const rule of all) {
|
|
49210
|
-
if (!rule.enforcement) rule.enforcement = "advisory";
|
|
49211
|
-
}
|
|
49212
|
-
return all.filter((rule) => {
|
|
49213
|
-
if (filters.severity && rule.severity !== filters.severity) return false;
|
|
49214
|
-
if (filters.enforcement && rule.enforcement !== filters.enforcement) return false;
|
|
49215
|
-
if (filters.targetUuid) {
|
|
49216
|
-
const hit = rule.targets.some(
|
|
49217
|
-
(t) => t.uuid === filters.targetUuid || t.entityUuid === filters.targetUuid
|
|
49218
|
-
);
|
|
49219
|
-
if (!hit) return false;
|
|
49220
|
-
}
|
|
49221
|
-
return true;
|
|
49222
|
-
});
|
|
49223
|
-
}
|
|
49224
|
-
/** Get a single rule by uuid (scans all scopes). */
|
|
49225
|
-
async getRule(uuid3) {
|
|
49226
|
-
const all = await this.listRules();
|
|
49227
|
-
return all.find((r) => r.uuid === uuid3) || null;
|
|
49228
|
-
}
|
|
49229
|
-
/** List rules whose targets include this entity (or any of its attributes). */
|
|
49230
|
-
async listRulesForEntity(entityUuid) {
|
|
49231
|
-
const all = await this.listRules();
|
|
49232
|
-
return all.filter(
|
|
49233
|
-
(rule) => rule.targets.some(
|
|
49234
|
-
(t) => t.uuid === entityUuid || t.entityUuid === entityUuid
|
|
49235
|
-
)
|
|
49236
|
-
);
|
|
49237
|
-
}
|
|
49238
|
-
/** Create a rule. Storage location is determined by `scope`. */
|
|
49239
|
-
async createRule(input) {
|
|
49240
|
-
if (!input.enforcement) input.enforcement = "advisory";
|
|
49241
|
-
const errors = validateRule(input);
|
|
49242
|
-
if (errors.length > 0) {
|
|
49243
|
-
return { success: false, errors };
|
|
49244
|
-
}
|
|
49245
|
-
let rule = {
|
|
49246
|
-
uuid: generateUUID(),
|
|
49247
|
-
name: input.name,
|
|
49248
|
-
description: input.description,
|
|
49249
|
-
severity: input.severity,
|
|
49250
|
-
enforcement: input.enforcement,
|
|
49251
|
-
scope: input.scope,
|
|
49252
|
-
targets: input.targets,
|
|
49253
|
-
packageName: input.packageName,
|
|
49254
|
-
entityUuid: input.entityUuid,
|
|
49255
|
-
caseUuid: input.caseUuid,
|
|
49256
|
-
expression: input.expression,
|
|
49257
|
-
tags: input.tags,
|
|
49258
|
-
metadata: input.metadata,
|
|
49259
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49260
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
49261
|
-
};
|
|
49262
|
-
rule = await this.applyAutoScope(rule);
|
|
49263
|
-
const saved = await this.saveRuleToScope(rule);
|
|
49264
|
-
if (!saved) return { success: false, errors: ["Failed to write rule to storage"] };
|
|
49265
|
-
return { success: true, rule };
|
|
49266
|
-
}
|
|
49267
|
-
/** Update an existing rule by uuid. Re-routes to new scope if scope changed. */
|
|
49268
|
-
async updateRule(uuid3, input) {
|
|
49269
|
-
const existing = await this.getRule(uuid3);
|
|
49270
|
-
if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
|
|
49271
|
-
let merged = {
|
|
49272
|
-
...existing,
|
|
49273
|
-
...input,
|
|
49274
|
-
uuid: existing.uuid,
|
|
49275
|
-
createdAt: existing.createdAt,
|
|
49276
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
49277
|
-
};
|
|
49278
|
-
const errors = validateRule(merged);
|
|
49279
|
-
if (errors.length > 0) return { success: false, errors };
|
|
49280
|
-
merged = await this.applyAutoScope(merged, existing.scope, existing.packageName);
|
|
49281
|
-
const scopeMoved = existing.scope !== merged.scope || existing.packageName !== merged.packageName || existing.entityUuid !== merged.entityUuid || existing.caseUuid !== merged.caseUuid;
|
|
49282
|
-
if (scopeMoved) {
|
|
49283
|
-
await this.removeRuleFromScope(existing);
|
|
49284
|
-
}
|
|
49285
|
-
const saved = await this.saveRuleToScope(merged);
|
|
49286
|
-
if (!saved) return { success: false, errors: ["Failed to write updated rule to storage"] };
|
|
49287
|
-
return { success: true, rule: merged };
|
|
49288
|
-
}
|
|
49289
|
-
/** Delete a rule by uuid. */
|
|
49290
|
-
async deleteRule(uuid3) {
|
|
49291
|
-
const existing = await this.getRule(uuid3);
|
|
49292
|
-
if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
|
|
49293
|
-
const removed = await this.removeRuleFromScope(existing);
|
|
49294
|
-
if (!removed) return { success: false, errors: ["Failed to remove rule from storage"] };
|
|
49295
|
-
return { success: true };
|
|
49296
|
-
}
|
|
49297
|
-
// ─── Private helpers: route to the right storage location ────────────────
|
|
49298
|
-
async saveRuleToScope(rule) {
|
|
49299
|
-
const projection = getProjection(wsId("dictionaries"));
|
|
49300
|
-
switch (rule.scope) {
|
|
49301
|
-
case "entity": {
|
|
49302
|
-
if (!rule.entityUuid) return false;
|
|
49303
|
-
const files = await listAllEntityRuleFiles();
|
|
49304
|
-
let service = files.find((f) => f.entityUuid === rule.entityUuid)?.service;
|
|
49305
|
-
if (!service) service = rule.packageName;
|
|
49306
|
-
if (!service) {
|
|
49307
|
-
logger.error(`Cannot determine service for entity ${rule.entityUuid}`);
|
|
49308
|
-
return false;
|
|
49309
|
-
}
|
|
49310
|
-
const entity = await this.findEntityByUuid(service, rule.entityUuid);
|
|
49311
|
-
if (!entity) {
|
|
49312
|
-
logger.error(`Entity ${rule.entityUuid} not found in service ${service}`);
|
|
49313
|
-
return false;
|
|
49314
|
-
}
|
|
49315
|
-
const existing = entity.rules || [];
|
|
49316
|
-
const updatedRules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
49317
|
-
entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
|
|
49318
|
-
const logicalPath = `packages/${service}/entities/${entity.name}`;
|
|
49319
|
-
try {
|
|
49320
|
-
await projection.writeEntity(logicalPath, entity);
|
|
49321
|
-
return true;
|
|
49322
|
-
} catch (e) {
|
|
49323
|
-
logger.error(`Failed to write entity-scope rule via projection: ${e.message}`);
|
|
49324
|
-
return false;
|
|
49325
|
-
}
|
|
49326
|
-
}
|
|
49327
|
-
case "package": {
|
|
49328
|
-
if (!rule.packageName) return false;
|
|
49329
|
-
const existing = await readPackageRules(rule.packageName);
|
|
49330
|
-
const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
49331
|
-
const packageLogicalPath = `packages/${rule.packageName}`;
|
|
49332
|
-
try {
|
|
49333
|
-
await projection.writePackageRules(packageLogicalPath, updated);
|
|
49334
|
-
return true;
|
|
49335
|
-
} catch (e) {
|
|
49336
|
-
logger.error(`Failed to write package-scope rules via projection: ${e.message}`);
|
|
49337
|
-
return false;
|
|
49338
|
-
}
|
|
49339
|
-
}
|
|
49340
|
-
case "case": {
|
|
49341
|
-
if (!rule.caseUuid) return false;
|
|
49342
|
-
const c = await readCaseFile(rule.caseUuid);
|
|
49343
|
-
if (!c) return false;
|
|
49344
|
-
const existing = c.rules || [];
|
|
49345
|
-
c.rules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
49346
|
-
c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49347
|
-
const targetPackage = await this.resolveCaseHomePackage(c);
|
|
49348
|
-
if (!targetPackage) return false;
|
|
49349
|
-
const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
|
|
49350
|
-
try {
|
|
49351
|
-
await projection.writeCase(caseLogicalPath, c);
|
|
49352
|
-
return true;
|
|
49353
|
-
} catch (e) {
|
|
49354
|
-
logger.error(`Failed to write case-scope rule via projection: ${e.message}`);
|
|
49355
|
-
return false;
|
|
49356
|
-
}
|
|
49357
|
-
}
|
|
49358
|
-
case "global": {
|
|
49359
|
-
const existing = await readGlobalRules();
|
|
49360
|
-
const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
49361
|
-
try {
|
|
49362
|
-
await projection.writeGlobalRules(updated);
|
|
49363
|
-
return true;
|
|
49364
|
-
} catch (e) {
|
|
49365
|
-
logger.error(`Failed to write global rules via projection: ${e.message}`);
|
|
49366
|
-
return false;
|
|
49367
|
-
}
|
|
49368
|
-
}
|
|
49369
|
-
default:
|
|
49370
|
-
return false;
|
|
49371
|
-
}
|
|
49372
|
-
}
|
|
49373
|
-
async removeRuleFromScope(rule) {
|
|
49374
|
-
const projection = getProjection(wsId("dictionaries"));
|
|
49375
|
-
switch (rule.scope) {
|
|
49376
|
-
case "entity": {
|
|
49377
|
-
if (!rule.entityUuid) return false;
|
|
49378
|
-
const files = await listAllEntityRuleFiles();
|
|
49379
|
-
const service = files.find((f) => f.entityUuid === rule.entityUuid)?.service ?? rule.packageName;
|
|
49380
|
-
if (!service) return false;
|
|
49381
|
-
const entity = await this.findEntityByUuid(service, rule.entityUuid);
|
|
49382
|
-
if (!entity) return false;
|
|
49383
|
-
const existing = entity.rules || [];
|
|
49384
|
-
const updatedRules = existing.filter((r) => r.uuid !== rule.uuid);
|
|
49385
|
-
entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
|
|
49386
|
-
const logicalPath = `packages/${service}/entities/${entity.name}`;
|
|
49387
|
-
try {
|
|
49388
|
-
await projection.writeEntity(logicalPath, entity);
|
|
49389
|
-
return true;
|
|
49390
|
-
} catch (e) {
|
|
49391
|
-
logger.error(`Failed to remove entity-scope rule via projection: ${e.message}`);
|
|
49392
|
-
return false;
|
|
49393
|
-
}
|
|
49394
|
-
}
|
|
49395
|
-
case "package": {
|
|
49396
|
-
if (!rule.packageName) return false;
|
|
49397
|
-
const existing = await readPackageRules(rule.packageName);
|
|
49398
|
-
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
49399
|
-
const packageLogicalPath = `packages/${rule.packageName}`;
|
|
49400
|
-
try {
|
|
49401
|
-
await projection.writePackageRules(packageLogicalPath, updated);
|
|
49402
|
-
return true;
|
|
49403
|
-
} catch (e) {
|
|
49404
|
-
logger.error(`Failed to remove package-scope rule via projection: ${e.message}`);
|
|
49405
|
-
return false;
|
|
49406
|
-
}
|
|
49407
|
-
}
|
|
49408
|
-
case "case": {
|
|
49409
|
-
if (!rule.caseUuid) return false;
|
|
49410
|
-
const c = await readCaseFile(rule.caseUuid);
|
|
49411
|
-
if (!c) return false;
|
|
49412
|
-
const existing = c.rules || [];
|
|
49413
|
-
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
49414
|
-
c.rules = updated.length > 0 ? updated : void 0;
|
|
49415
|
-
c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49416
|
-
const targetPackage = await this.resolveCaseHomePackage(c);
|
|
49417
|
-
if (!targetPackage) return false;
|
|
49418
|
-
const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
|
|
49419
|
-
try {
|
|
49420
|
-
await projection.writeCase(caseLogicalPath, c);
|
|
49421
|
-
return true;
|
|
49422
|
-
} catch (e) {
|
|
49423
|
-
logger.error(`Failed to remove case-scope rule via projection: ${e.message}`);
|
|
49424
|
-
return false;
|
|
49425
|
-
}
|
|
49426
|
-
}
|
|
49427
|
-
case "global": {
|
|
49428
|
-
const existing = await readGlobalRules();
|
|
49429
|
-
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
49430
|
-
try {
|
|
49431
|
-
await projection.writeGlobalRules(updated);
|
|
49432
|
-
return true;
|
|
49433
|
-
} catch (e) {
|
|
49434
|
-
logger.error(`Failed to remove global rule via projection: ${e.message}`);
|
|
49435
|
-
return false;
|
|
49436
|
-
}
|
|
49437
|
-
}
|
|
49438
|
-
default:
|
|
49439
|
-
return false;
|
|
49440
|
-
}
|
|
49441
|
-
}
|
|
49442
|
-
/**
|
|
49443
|
-
* Load an entity from a package by uuid. Returns null if not found.
|
|
49444
|
-
* Used by entity-scope rule writes to round-trip the parent entity
|
|
49445
|
-
* through `projection.writeEntity`.
|
|
49446
|
-
*/
|
|
49447
|
-
async findEntityByUuid(service, entityUuid) {
|
|
49448
|
-
try {
|
|
49449
|
-
const pkg = await loadPackage(service);
|
|
49450
|
-
return pkg.entities.find((e) => e.uuid === entityUuid) ?? null;
|
|
49451
|
-
} catch (error48) {
|
|
49452
|
-
logger.error(`Error loading package ${service} for entity lookup: ${error48}`);
|
|
49453
|
-
return null;
|
|
49454
|
-
}
|
|
49455
|
-
}
|
|
49456
|
-
/**
|
|
49457
|
-
* Resolve the home package for a case so we can construct a
|
|
49458
|
-
* `packages/<pkg>/cases/<name>` logical path. Mirrors the heuristic in
|
|
49459
|
-
* caseService.ts / fileOperations.ts: existing-owner lookup by uuid first,
|
|
49460
|
-
* then first root entity's package, then first package on disk.
|
|
49461
|
-
*/
|
|
49462
|
-
async resolveCaseHomePackage(c) {
|
|
49463
|
-
const { listPackages: listPackages3 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
49464
|
-
const packages = await listPackages3();
|
|
49465
|
-
for (const pkg of packages) {
|
|
49466
|
-
try {
|
|
49467
|
-
const model = await loadPackage(pkg);
|
|
49468
|
-
if (model.ownership.caseByUuid.has(c.uuid)) return pkg;
|
|
49469
|
-
} catch {
|
|
49470
|
-
}
|
|
49471
|
-
}
|
|
49472
|
-
if (c.rootEntities && c.rootEntities.length > 0) {
|
|
49473
|
-
const rootUuid = c.rootEntities[0];
|
|
49474
|
-
for (const pkg of packages) {
|
|
49475
|
-
try {
|
|
49476
|
-
const model = await loadPackage(pkg);
|
|
49477
|
-
if (model.ownership.entityByUuid.has(rootUuid)) return pkg;
|
|
49478
|
-
} catch {
|
|
49479
|
-
}
|
|
49480
|
-
}
|
|
49481
|
-
}
|
|
49482
|
-
return packages[0] || null;
|
|
49483
|
-
}
|
|
49484
|
-
// ─── Auto-promote / auto-demote (#75) ────────────────────────────────
|
|
49485
|
-
/**
|
|
49486
|
-
* Determine the correct scope for a rule based on its targets.
|
|
49487
|
-
* Called before save. If targets span 2+ packages → global.
|
|
49488
|
-
* If all targets are in one package → that package's scope (keep
|
|
49489
|
-
* the caller's intent for entity vs package).
|
|
49490
|
-
*/
|
|
49491
|
-
resolveScope(rule) {
|
|
49492
|
-
const packages = /* @__PURE__ */ new Set();
|
|
49493
|
-
for (const t of rule.targets) {
|
|
49494
|
-
if (t.packageName) packages.add(t.packageName);
|
|
49495
|
-
}
|
|
49496
|
-
if (packages.size >= 2) {
|
|
49497
|
-
return { scope: "global" };
|
|
49498
|
-
}
|
|
49499
|
-
if (packages.size === 1) {
|
|
49500
|
-
const pkg = [...packages][0];
|
|
49501
|
-
if (rule.scope === "global") {
|
|
49502
|
-
return { scope: "package", packageName: pkg };
|
|
49503
|
-
}
|
|
49504
|
-
}
|
|
49505
|
-
return { scope: rule.scope, packageName: rule.packageName };
|
|
49506
|
-
}
|
|
49507
|
-
/**
|
|
49508
|
-
* Wrap around createRule/updateRule to apply auto-promote/demote.
|
|
49509
|
-
* Checks if the resolved scope differs from what the caller requested
|
|
49510
|
-
* and adjusts accordingly — including moving the rule between files.
|
|
49511
|
-
*/
|
|
49512
|
-
async applyAutoScope(rule, _oldScope, _oldPkg) {
|
|
49513
|
-
const resolved = this.resolveScope(rule);
|
|
49514
|
-
if (resolved.scope !== rule.scope) {
|
|
49515
|
-
logger.info(`Auto-${resolved.scope === "global" ? "promote" : "demote"} rule ${rule.uuid}: ${rule.scope} \u2192 ${resolved.scope}`);
|
|
49516
|
-
}
|
|
49517
|
-
rule.scope = resolved.scope;
|
|
49518
|
-
if (resolved.packageName !== void 0) {
|
|
49519
|
-
rule.packageName = resolved.packageName;
|
|
49520
|
-
}
|
|
49521
|
-
return rule;
|
|
49522
|
-
}
|
|
49523
|
-
};
|
|
49524
|
-
ruleService = new RuleService();
|
|
49525
|
-
}
|
|
49526
|
-
});
|
|
49527
|
-
|
|
49528
49167
|
// backend/node_modules/zod/v4/core/core.js
|
|
49529
49168
|
// @__NO_SIDE_EFFECTS__
|
|
49530
49169
|
function $constructor(name21, initializer3, params) {
|
|
@@ -50508,7 +50147,7 @@ var init_errors = __esm({
|
|
|
50508
50147
|
});
|
|
50509
50148
|
|
|
50510
50149
|
// backend/node_modules/zod/v4/core/parse.js
|
|
50511
|
-
var _parse,
|
|
50150
|
+
var _parse, parse3, _parseAsync, parseAsync, _safeParse, safeParse, _safeParseAsync, safeParseAsync, _encode, encode, _decode, decode, _encodeAsync, encodeAsync, _decodeAsync, decodeAsync, _safeEncode, safeEncode, _safeDecode, safeDecode, _safeEncodeAsync, safeEncodeAsync, _safeDecodeAsync, safeDecodeAsync;
|
|
50512
50151
|
var init_parse = __esm({
|
|
50513
50152
|
"backend/node_modules/zod/v4/core/parse.js"() {
|
|
50514
50153
|
init_core();
|
|
@@ -50527,7 +50166,7 @@ var init_parse = __esm({
|
|
|
50527
50166
|
}
|
|
50528
50167
|
return result.value;
|
|
50529
50168
|
}, "_parse");
|
|
50530
|
-
|
|
50169
|
+
parse3 = /* @__PURE__ */ _parse($ZodRealError);
|
|
50531
50170
|
_parseAsync = /* @__PURE__ */ __name((_Err) => async (schema, value, _ctx, params) => {
|
|
50532
50171
|
const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
|
|
50533
50172
|
let result = schema._zod.run({ value, issues: [] }, ctx);
|
|
@@ -53285,10 +52924,10 @@ var init_schemas = __esm({
|
|
|
53285
52924
|
throw new Error("implement() must be called with a function");
|
|
53286
52925
|
}
|
|
53287
52926
|
return function(...args) {
|
|
53288
|
-
const parsedArgs = inst._def.input ?
|
|
52927
|
+
const parsedArgs = inst._def.input ? parse3(inst._def.input, args) : args;
|
|
53289
52928
|
const result = Reflect.apply(func, this, parsedArgs);
|
|
53290
52929
|
if (inst._def.output) {
|
|
53291
|
-
return
|
|
52930
|
+
return parse3(inst._def.output, result);
|
|
53292
52931
|
}
|
|
53293
52932
|
return result;
|
|
53294
52933
|
};
|
|
@@ -61906,7 +61545,7 @@ __export(core_exports2, {
|
|
|
61906
61545
|
isValidJWT: () => isValidJWT,
|
|
61907
61546
|
locales: () => locales_exports,
|
|
61908
61547
|
meta: () => meta,
|
|
61909
|
-
parse: () =>
|
|
61548
|
+
parse: () => parse3,
|
|
61910
61549
|
parseAsync: () => parseAsync,
|
|
61911
61550
|
prettifyError: () => prettifyError,
|
|
61912
61551
|
process: () => process2,
|
|
@@ -62085,12 +61724,12 @@ var init_errors2 = __esm({
|
|
|
62085
61724
|
});
|
|
62086
61725
|
|
|
62087
61726
|
// backend/node_modules/zod/v4/classic/parse.js
|
|
62088
|
-
var
|
|
61727
|
+
var parse4, parseAsync2, safeParse2, safeParseAsync2, encode2, decode2, encodeAsync2, decodeAsync2, safeEncode2, safeDecode2, safeEncodeAsync2, safeDecodeAsync2;
|
|
62089
61728
|
var init_parse2 = __esm({
|
|
62090
61729
|
"backend/node_modules/zod/v4/classic/parse.js"() {
|
|
62091
61730
|
init_core2();
|
|
62092
61731
|
init_errors2();
|
|
62093
|
-
|
|
61732
|
+
parse4 = /* @__PURE__ */ _parse(ZodRealError);
|
|
62094
61733
|
parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
62095
61734
|
safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
62096
61735
|
safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -62766,7 +62405,7 @@ var init_schemas2 = __esm({
|
|
|
62766
62405
|
reg.add(inst, meta3);
|
|
62767
62406
|
return inst;
|
|
62768
62407
|
});
|
|
62769
|
-
inst.parse = (data, params) =>
|
|
62408
|
+
inst.parse = (data, params) => parse4(inst, data, params, { callee: inst.parse });
|
|
62770
62409
|
inst.safeParse = (data, params) => safeParse2(inst, data, params);
|
|
62771
62410
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
62772
62411
|
inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
|
|
@@ -64193,7 +63832,7 @@ __export(external_exports, {
|
|
|
64193
63832
|
object: () => object,
|
|
64194
63833
|
optional: () => optional,
|
|
64195
63834
|
overwrite: () => _overwrite,
|
|
64196
|
-
parse: () =>
|
|
63835
|
+
parse: () => parse4,
|
|
64197
63836
|
parseAsync: () => parseAsync2,
|
|
64198
63837
|
partialRecord: () => partialRecord,
|
|
64199
63838
|
pipe: () => pipe,
|
|
@@ -64308,6 +63947,424 @@ var init_agentToolRegistry = __esm({
|
|
|
64308
63947
|
}
|
|
64309
63948
|
});
|
|
64310
63949
|
|
|
63950
|
+
// backend/src/models/Rule.ts
|
|
63951
|
+
function validateRule(rule) {
|
|
63952
|
+
const errors = [];
|
|
63953
|
+
if (!rule.name || typeof rule.name !== "string") {
|
|
63954
|
+
errors.push("Rule name is required");
|
|
63955
|
+
} else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rule.name)) {
|
|
63956
|
+
errors.push("Rule name must be kebab-case (lowercase letters, numbers, hyphens)");
|
|
63957
|
+
}
|
|
63958
|
+
if (!rule.description || typeof rule.description !== "string") {
|
|
63959
|
+
errors.push("Rule description is required");
|
|
63960
|
+
}
|
|
63961
|
+
if (!rule.severity || !["info", "warning", "error"].includes(rule.severity)) {
|
|
63962
|
+
errors.push("Rule severity must be one of: info, warning, error");
|
|
63963
|
+
}
|
|
63964
|
+
if (!rule.enforcement || !["save", "process", "advisory"].includes(rule.enforcement)) {
|
|
63965
|
+
errors.push("Rule enforcement must be one of: save, process, advisory");
|
|
63966
|
+
}
|
|
63967
|
+
if (!rule.scope || !["entity", "package", "case", "global"].includes(rule.scope)) {
|
|
63968
|
+
errors.push("Rule scope must be one of: entity, package, case, global");
|
|
63969
|
+
}
|
|
63970
|
+
if (!Array.isArray(rule.targets) || rule.targets.length === 0) {
|
|
63971
|
+
errors.push("Rule must have at least one target");
|
|
63972
|
+
}
|
|
63973
|
+
if (rule.scope === "entity" && !rule.entityUuid) {
|
|
63974
|
+
errors.push("Entity-scoped rules must specify entityUuid");
|
|
63975
|
+
}
|
|
63976
|
+
if (rule.scope === "package" && !rule.packageName) {
|
|
63977
|
+
errors.push("Package-scoped rules must specify packageName");
|
|
63978
|
+
}
|
|
63979
|
+
if (rule.scope === "case" && !rule.caseUuid) {
|
|
63980
|
+
errors.push("Case-scoped rules must specify caseUuid");
|
|
63981
|
+
}
|
|
63982
|
+
if (rule.enforcement === "process") {
|
|
63983
|
+
const stageField = (rule.metadata || []).find((m) => m.name === "process-stage-field");
|
|
63984
|
+
if (!stageField || !stageField.value) {
|
|
63985
|
+
errors.push("Process-enforcement rules must reference a metadata field via process-stage-field");
|
|
63986
|
+
}
|
|
63987
|
+
}
|
|
63988
|
+
return errors;
|
|
63989
|
+
}
|
|
63990
|
+
var init_Rule = __esm({
|
|
63991
|
+
"backend/src/models/Rule.ts"() {
|
|
63992
|
+
"use strict";
|
|
63993
|
+
__name(validateRule, "validateRule");
|
|
63994
|
+
}
|
|
63995
|
+
});
|
|
63996
|
+
|
|
63997
|
+
// backend/src/services/ruleService.ts
|
|
63998
|
+
var ruleService_exports = {};
|
|
63999
|
+
__export(ruleService_exports, {
|
|
64000
|
+
ruleService: () => ruleService
|
|
64001
|
+
});
|
|
64002
|
+
var RuleService, ruleService;
|
|
64003
|
+
var init_ruleService = __esm({
|
|
64004
|
+
"backend/src/services/ruleService.ts"() {
|
|
64005
|
+
"use strict";
|
|
64006
|
+
init_Rule();
|
|
64007
|
+
init_fileOperations();
|
|
64008
|
+
init_uuid();
|
|
64009
|
+
init_logger();
|
|
64010
|
+
init_ProjectionRegistry();
|
|
64011
|
+
init_types();
|
|
64012
|
+
RuleService = class {
|
|
64013
|
+
static {
|
|
64014
|
+
__name(this, "RuleService");
|
|
64015
|
+
}
|
|
64016
|
+
/** List all rules from all storage scopes, optionally filtered. */
|
|
64017
|
+
async listRules(filters = {}) {
|
|
64018
|
+
const all = [];
|
|
64019
|
+
if (!filters.scope || filters.scope === "entity") {
|
|
64020
|
+
const files = await listAllEntityRuleFiles();
|
|
64021
|
+
for (const { service, entityUuid } of files) {
|
|
64022
|
+
const rules = await readEntityRules(service, entityUuid);
|
|
64023
|
+
all.push(...rules);
|
|
64024
|
+
}
|
|
64025
|
+
}
|
|
64026
|
+
if (!filters.scope || filters.scope === "package") {
|
|
64027
|
+
const packages = filters.packageName ? [filters.packageName] : await listPackagesWithRules();
|
|
64028
|
+
for (const pkg of packages) {
|
|
64029
|
+
const rules = await readPackageRules(pkg);
|
|
64030
|
+
all.push(...rules);
|
|
64031
|
+
}
|
|
64032
|
+
}
|
|
64033
|
+
if (!filters.scope || filters.scope === "global") {
|
|
64034
|
+
const globalRules = await readGlobalRules();
|
|
64035
|
+
all.push(...globalRules);
|
|
64036
|
+
}
|
|
64037
|
+
if (!filters.scope || filters.scope === "case") {
|
|
64038
|
+
if (filters.caseUuid) {
|
|
64039
|
+
const rules = await readCaseRules(filters.caseUuid);
|
|
64040
|
+
all.push(...rules);
|
|
64041
|
+
} else {
|
|
64042
|
+
const cases = await listCases();
|
|
64043
|
+
for (const c of cases) {
|
|
64044
|
+
const rules = c.rules || [];
|
|
64045
|
+
all.push(...rules);
|
|
64046
|
+
}
|
|
64047
|
+
}
|
|
64048
|
+
}
|
|
64049
|
+
for (const rule of all) {
|
|
64050
|
+
if (!rule.enforcement) rule.enforcement = "advisory";
|
|
64051
|
+
}
|
|
64052
|
+
return all.filter((rule) => {
|
|
64053
|
+
if (filters.severity && rule.severity !== filters.severity) return false;
|
|
64054
|
+
if (filters.enforcement && rule.enforcement !== filters.enforcement) return false;
|
|
64055
|
+
if (filters.targetUuid) {
|
|
64056
|
+
const hit = rule.targets.some(
|
|
64057
|
+
(t) => t.uuid === filters.targetUuid || t.entityUuid === filters.targetUuid
|
|
64058
|
+
);
|
|
64059
|
+
if (!hit) return false;
|
|
64060
|
+
}
|
|
64061
|
+
return true;
|
|
64062
|
+
});
|
|
64063
|
+
}
|
|
64064
|
+
/** Get a single rule by uuid (scans all scopes). */
|
|
64065
|
+
async getRule(uuid3) {
|
|
64066
|
+
const all = await this.listRules();
|
|
64067
|
+
return all.find((r) => r.uuid === uuid3) || null;
|
|
64068
|
+
}
|
|
64069
|
+
/** List rules whose targets include this entity (or any of its attributes). */
|
|
64070
|
+
async listRulesForEntity(entityUuid) {
|
|
64071
|
+
const all = await this.listRules();
|
|
64072
|
+
return all.filter(
|
|
64073
|
+
(rule) => rule.targets.some(
|
|
64074
|
+
(t) => t.uuid === entityUuid || t.entityUuid === entityUuid
|
|
64075
|
+
)
|
|
64076
|
+
);
|
|
64077
|
+
}
|
|
64078
|
+
/** Create a rule. Storage location is determined by `scope`. */
|
|
64079
|
+
async createRule(input) {
|
|
64080
|
+
if (!input.enforcement) input.enforcement = "advisory";
|
|
64081
|
+
const errors = validateRule(input);
|
|
64082
|
+
if (errors.length > 0) {
|
|
64083
|
+
return { success: false, errors };
|
|
64084
|
+
}
|
|
64085
|
+
let rule = {
|
|
64086
|
+
uuid: generateUUID(),
|
|
64087
|
+
name: input.name,
|
|
64088
|
+
description: input.description,
|
|
64089
|
+
severity: input.severity,
|
|
64090
|
+
enforcement: input.enforcement,
|
|
64091
|
+
scope: input.scope,
|
|
64092
|
+
targets: input.targets,
|
|
64093
|
+
packageName: input.packageName,
|
|
64094
|
+
entityUuid: input.entityUuid,
|
|
64095
|
+
caseUuid: input.caseUuid,
|
|
64096
|
+
expression: input.expression,
|
|
64097
|
+
tags: input.tags,
|
|
64098
|
+
metadata: input.metadata,
|
|
64099
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
64100
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
64101
|
+
};
|
|
64102
|
+
rule = await this.applyAutoScope(rule);
|
|
64103
|
+
const saved = await this.saveRuleToScope(rule);
|
|
64104
|
+
if (!saved) return { success: false, errors: ["Failed to write rule to storage"] };
|
|
64105
|
+
return { success: true, rule };
|
|
64106
|
+
}
|
|
64107
|
+
/** Update an existing rule by uuid. Re-routes to new scope if scope changed. */
|
|
64108
|
+
async updateRule(uuid3, input) {
|
|
64109
|
+
const existing = await this.getRule(uuid3);
|
|
64110
|
+
if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
|
|
64111
|
+
let merged = {
|
|
64112
|
+
...existing,
|
|
64113
|
+
...input,
|
|
64114
|
+
uuid: existing.uuid,
|
|
64115
|
+
createdAt: existing.createdAt,
|
|
64116
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
64117
|
+
};
|
|
64118
|
+
const errors = validateRule(merged);
|
|
64119
|
+
if (errors.length > 0) return { success: false, errors };
|
|
64120
|
+
merged = await this.applyAutoScope(merged, existing.scope, existing.packageName);
|
|
64121
|
+
const scopeMoved = existing.scope !== merged.scope || existing.packageName !== merged.packageName || existing.entityUuid !== merged.entityUuid || existing.caseUuid !== merged.caseUuid;
|
|
64122
|
+
if (scopeMoved) {
|
|
64123
|
+
await this.removeRuleFromScope(existing);
|
|
64124
|
+
}
|
|
64125
|
+
const saved = await this.saveRuleToScope(merged);
|
|
64126
|
+
if (!saved) return { success: false, errors: ["Failed to write updated rule to storage"] };
|
|
64127
|
+
return { success: true, rule: merged };
|
|
64128
|
+
}
|
|
64129
|
+
/** Delete a rule by uuid. */
|
|
64130
|
+
async deleteRule(uuid3) {
|
|
64131
|
+
const existing = await this.getRule(uuid3);
|
|
64132
|
+
if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
|
|
64133
|
+
const removed = await this.removeRuleFromScope(existing);
|
|
64134
|
+
if (!removed) return { success: false, errors: ["Failed to remove rule from storage"] };
|
|
64135
|
+
return { success: true };
|
|
64136
|
+
}
|
|
64137
|
+
// ─── Private helpers: route to the right storage location ────────────────
|
|
64138
|
+
async saveRuleToScope(rule) {
|
|
64139
|
+
const projection = getProjection(wsId("dictionaries"));
|
|
64140
|
+
switch (rule.scope) {
|
|
64141
|
+
case "entity": {
|
|
64142
|
+
if (!rule.entityUuid) return false;
|
|
64143
|
+
const files = await listAllEntityRuleFiles();
|
|
64144
|
+
let service = files.find((f) => f.entityUuid === rule.entityUuid)?.service;
|
|
64145
|
+
if (!service) service = rule.packageName;
|
|
64146
|
+
if (!service) {
|
|
64147
|
+
logger.error(`Cannot determine service for entity ${rule.entityUuid}`);
|
|
64148
|
+
return false;
|
|
64149
|
+
}
|
|
64150
|
+
const entity = await this.findEntityByUuid(service, rule.entityUuid);
|
|
64151
|
+
if (!entity) {
|
|
64152
|
+
logger.error(`Entity ${rule.entityUuid} not found in service ${service}`);
|
|
64153
|
+
return false;
|
|
64154
|
+
}
|
|
64155
|
+
const existing = entity.rules || [];
|
|
64156
|
+
const updatedRules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
64157
|
+
entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
|
|
64158
|
+
const logicalPath = `packages/${service}/entities/${entity.name}`;
|
|
64159
|
+
try {
|
|
64160
|
+
await projection.writeEntity(logicalPath, entity);
|
|
64161
|
+
return true;
|
|
64162
|
+
} catch (e) {
|
|
64163
|
+
logger.error(`Failed to write entity-scope rule via projection: ${e.message}`);
|
|
64164
|
+
return false;
|
|
64165
|
+
}
|
|
64166
|
+
}
|
|
64167
|
+
case "package": {
|
|
64168
|
+
if (!rule.packageName) return false;
|
|
64169
|
+
const existing = await readPackageRules(rule.packageName);
|
|
64170
|
+
const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
64171
|
+
const packageLogicalPath = `packages/${rule.packageName}`;
|
|
64172
|
+
try {
|
|
64173
|
+
await projection.writePackageRules(packageLogicalPath, updated);
|
|
64174
|
+
return true;
|
|
64175
|
+
} catch (e) {
|
|
64176
|
+
logger.error(`Failed to write package-scope rules via projection: ${e.message}`);
|
|
64177
|
+
return false;
|
|
64178
|
+
}
|
|
64179
|
+
}
|
|
64180
|
+
case "case": {
|
|
64181
|
+
if (!rule.caseUuid) return false;
|
|
64182
|
+
const c = await readCaseFile(rule.caseUuid);
|
|
64183
|
+
if (!c) return false;
|
|
64184
|
+
const existing = c.rules || [];
|
|
64185
|
+
c.rules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
64186
|
+
c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
64187
|
+
const targetPackage = await this.resolveCaseHomePackage(c);
|
|
64188
|
+
if (!targetPackage) return false;
|
|
64189
|
+
const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
|
|
64190
|
+
try {
|
|
64191
|
+
await projection.writeCase(caseLogicalPath, c);
|
|
64192
|
+
return true;
|
|
64193
|
+
} catch (e) {
|
|
64194
|
+
logger.error(`Failed to write case-scope rule via projection: ${e.message}`);
|
|
64195
|
+
return false;
|
|
64196
|
+
}
|
|
64197
|
+
}
|
|
64198
|
+
case "global": {
|
|
64199
|
+
const existing = await readGlobalRules();
|
|
64200
|
+
const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
|
|
64201
|
+
try {
|
|
64202
|
+
await projection.writeGlobalRules(updated);
|
|
64203
|
+
return true;
|
|
64204
|
+
} catch (e) {
|
|
64205
|
+
logger.error(`Failed to write global rules via projection: ${e.message}`);
|
|
64206
|
+
return false;
|
|
64207
|
+
}
|
|
64208
|
+
}
|
|
64209
|
+
default:
|
|
64210
|
+
return false;
|
|
64211
|
+
}
|
|
64212
|
+
}
|
|
64213
|
+
async removeRuleFromScope(rule) {
|
|
64214
|
+
const projection = getProjection(wsId("dictionaries"));
|
|
64215
|
+
switch (rule.scope) {
|
|
64216
|
+
case "entity": {
|
|
64217
|
+
if (!rule.entityUuid) return false;
|
|
64218
|
+
const files = await listAllEntityRuleFiles();
|
|
64219
|
+
const service = files.find((f) => f.entityUuid === rule.entityUuid)?.service ?? rule.packageName;
|
|
64220
|
+
if (!service) return false;
|
|
64221
|
+
const entity = await this.findEntityByUuid(service, rule.entityUuid);
|
|
64222
|
+
if (!entity) return false;
|
|
64223
|
+
const existing = entity.rules || [];
|
|
64224
|
+
const updatedRules = existing.filter((r) => r.uuid !== rule.uuid);
|
|
64225
|
+
entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
|
|
64226
|
+
const logicalPath = `packages/${service}/entities/${entity.name}`;
|
|
64227
|
+
try {
|
|
64228
|
+
await projection.writeEntity(logicalPath, entity);
|
|
64229
|
+
return true;
|
|
64230
|
+
} catch (e) {
|
|
64231
|
+
logger.error(`Failed to remove entity-scope rule via projection: ${e.message}`);
|
|
64232
|
+
return false;
|
|
64233
|
+
}
|
|
64234
|
+
}
|
|
64235
|
+
case "package": {
|
|
64236
|
+
if (!rule.packageName) return false;
|
|
64237
|
+
const existing = await readPackageRules(rule.packageName);
|
|
64238
|
+
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
64239
|
+
const packageLogicalPath = `packages/${rule.packageName}`;
|
|
64240
|
+
try {
|
|
64241
|
+
await projection.writePackageRules(packageLogicalPath, updated);
|
|
64242
|
+
return true;
|
|
64243
|
+
} catch (e) {
|
|
64244
|
+
logger.error(`Failed to remove package-scope rule via projection: ${e.message}`);
|
|
64245
|
+
return false;
|
|
64246
|
+
}
|
|
64247
|
+
}
|
|
64248
|
+
case "case": {
|
|
64249
|
+
if (!rule.caseUuid) return false;
|
|
64250
|
+
const c = await readCaseFile(rule.caseUuid);
|
|
64251
|
+
if (!c) return false;
|
|
64252
|
+
const existing = c.rules || [];
|
|
64253
|
+
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
64254
|
+
c.rules = updated.length > 0 ? updated : void 0;
|
|
64255
|
+
c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
64256
|
+
const targetPackage = await this.resolveCaseHomePackage(c);
|
|
64257
|
+
if (!targetPackage) return false;
|
|
64258
|
+
const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
|
|
64259
|
+
try {
|
|
64260
|
+
await projection.writeCase(caseLogicalPath, c);
|
|
64261
|
+
return true;
|
|
64262
|
+
} catch (e) {
|
|
64263
|
+
logger.error(`Failed to remove case-scope rule via projection: ${e.message}`);
|
|
64264
|
+
return false;
|
|
64265
|
+
}
|
|
64266
|
+
}
|
|
64267
|
+
case "global": {
|
|
64268
|
+
const existing = await readGlobalRules();
|
|
64269
|
+
const updated = existing.filter((r) => r.uuid !== rule.uuid);
|
|
64270
|
+
try {
|
|
64271
|
+
await projection.writeGlobalRules(updated);
|
|
64272
|
+
return true;
|
|
64273
|
+
} catch (e) {
|
|
64274
|
+
logger.error(`Failed to remove global rule via projection: ${e.message}`);
|
|
64275
|
+
return false;
|
|
64276
|
+
}
|
|
64277
|
+
}
|
|
64278
|
+
default:
|
|
64279
|
+
return false;
|
|
64280
|
+
}
|
|
64281
|
+
}
|
|
64282
|
+
/**
|
|
64283
|
+
* Load an entity from a package by uuid. Returns null if not found.
|
|
64284
|
+
* Used by entity-scope rule writes to round-trip the parent entity
|
|
64285
|
+
* through `projection.writeEntity`.
|
|
64286
|
+
*/
|
|
64287
|
+
async findEntityByUuid(service, entityUuid) {
|
|
64288
|
+
try {
|
|
64289
|
+
const pkg = await loadPackage(service);
|
|
64290
|
+
return pkg.entities.find((e) => e.uuid === entityUuid) ?? null;
|
|
64291
|
+
} catch (error48) {
|
|
64292
|
+
logger.error(`Error loading package ${service} for entity lookup: ${error48}`);
|
|
64293
|
+
return null;
|
|
64294
|
+
}
|
|
64295
|
+
}
|
|
64296
|
+
/**
|
|
64297
|
+
* Resolve the home package for a case so we can construct a
|
|
64298
|
+
* `packages/<pkg>/cases/<name>` logical path. Mirrors the heuristic in
|
|
64299
|
+
* caseService.ts / fileOperations.ts: existing-owner lookup by uuid first,
|
|
64300
|
+
* then first root entity's package, then first package on disk.
|
|
64301
|
+
*/
|
|
64302
|
+
async resolveCaseHomePackage(c) {
|
|
64303
|
+
const { listPackages: listPackages3 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
64304
|
+
const packages = await listPackages3();
|
|
64305
|
+
for (const pkg of packages) {
|
|
64306
|
+
try {
|
|
64307
|
+
const model = await loadPackage(pkg);
|
|
64308
|
+
if (model.ownership.caseByUuid.has(c.uuid)) return pkg;
|
|
64309
|
+
} catch {
|
|
64310
|
+
}
|
|
64311
|
+
}
|
|
64312
|
+
if (c.rootEntities && c.rootEntities.length > 0) {
|
|
64313
|
+
const rootUuid = c.rootEntities[0];
|
|
64314
|
+
for (const pkg of packages) {
|
|
64315
|
+
try {
|
|
64316
|
+
const model = await loadPackage(pkg);
|
|
64317
|
+
if (model.ownership.entityByUuid.has(rootUuid)) return pkg;
|
|
64318
|
+
} catch {
|
|
64319
|
+
}
|
|
64320
|
+
}
|
|
64321
|
+
}
|
|
64322
|
+
return packages[0] || null;
|
|
64323
|
+
}
|
|
64324
|
+
// ─── Auto-promote / auto-demote (#75) ────────────────────────────────
|
|
64325
|
+
/**
|
|
64326
|
+
* Determine the correct scope for a rule based on its targets.
|
|
64327
|
+
* Called before save. If targets span 2+ packages → global.
|
|
64328
|
+
* If all targets are in one package → that package's scope (keep
|
|
64329
|
+
* the caller's intent for entity vs package).
|
|
64330
|
+
*/
|
|
64331
|
+
resolveScope(rule) {
|
|
64332
|
+
const packages = /* @__PURE__ */ new Set();
|
|
64333
|
+
for (const t of rule.targets) {
|
|
64334
|
+
if (t.packageName) packages.add(t.packageName);
|
|
64335
|
+
}
|
|
64336
|
+
if (packages.size >= 2) {
|
|
64337
|
+
return { scope: "global" };
|
|
64338
|
+
}
|
|
64339
|
+
if (packages.size === 1) {
|
|
64340
|
+
const pkg = [...packages][0];
|
|
64341
|
+
if (rule.scope === "global") {
|
|
64342
|
+
return { scope: "package", packageName: pkg };
|
|
64343
|
+
}
|
|
64344
|
+
}
|
|
64345
|
+
return { scope: rule.scope, packageName: rule.packageName };
|
|
64346
|
+
}
|
|
64347
|
+
/**
|
|
64348
|
+
* Wrap around createRule/updateRule to apply auto-promote/demote.
|
|
64349
|
+
* Checks if the resolved scope differs from what the caller requested
|
|
64350
|
+
* and adjusts accordingly — including moving the rule between files.
|
|
64351
|
+
*/
|
|
64352
|
+
async applyAutoScope(rule, _oldScope, _oldPkg) {
|
|
64353
|
+
const resolved = this.resolveScope(rule);
|
|
64354
|
+
if (resolved.scope !== rule.scope) {
|
|
64355
|
+
logger.info(`Auto-${resolved.scope === "global" ? "promote" : "demote"} rule ${rule.uuid}: ${rule.scope} \u2192 ${resolved.scope}`);
|
|
64356
|
+
}
|
|
64357
|
+
rule.scope = resolved.scope;
|
|
64358
|
+
if (resolved.packageName !== void 0) {
|
|
64359
|
+
rule.packageName = resolved.packageName;
|
|
64360
|
+
}
|
|
64361
|
+
return rule;
|
|
64362
|
+
}
|
|
64363
|
+
};
|
|
64364
|
+
ruleService = new RuleService();
|
|
64365
|
+
}
|
|
64366
|
+
});
|
|
64367
|
+
|
|
64311
64368
|
// backend/src/services/versionService.ts
|
|
64312
64369
|
var versionService_exports = {};
|
|
64313
64370
|
__export(versionService_exports, {
|
|
@@ -83464,6 +83521,79 @@ var init_authoringRules = __esm({
|
|
|
83464
83521
|
}
|
|
83465
83522
|
});
|
|
83466
83523
|
|
|
83524
|
+
// backend/src/services/ai/physicalMapping.ts
|
|
83525
|
+
function metaValue2(meta3, name21) {
|
|
83526
|
+
const e = meta3?.find((m) => m.name === name21);
|
|
83527
|
+
return e && e.value != null ? String(e.value) : void 0;
|
|
83528
|
+
}
|
|
83529
|
+
function metaFlag(meta3, name21) {
|
|
83530
|
+
const v = meta3?.find((m) => m.name === name21)?.value;
|
|
83531
|
+
return v === true || /^(true|yes|1)$/i.test(String(v ?? ""));
|
|
83532
|
+
}
|
|
83533
|
+
function resolveAttributePhysical(a) {
|
|
83534
|
+
const columnName = metaValue2(a.metadata, "physical.columnName");
|
|
83535
|
+
const dbType = metaValue2(a.metadata, "physical.dbType");
|
|
83536
|
+
return {
|
|
83537
|
+
...columnName ? { columnName } : {},
|
|
83538
|
+
...dbType ? { dbType } : {},
|
|
83539
|
+
// == null: a YAML `primaryKey:` left empty means "unset", not false —
|
|
83540
|
+
// fall back to the legacy metadata flag in that case too.
|
|
83541
|
+
primaryKey: a.primaryKey == null ? metaFlag(a.metadata, "isPrimaryKey") : !!a.primaryKey
|
|
83542
|
+
};
|
|
83543
|
+
}
|
|
83544
|
+
var init_physicalMapping = __esm({
|
|
83545
|
+
"backend/src/services/ai/physicalMapping.ts"() {
|
|
83546
|
+
"use strict";
|
|
83547
|
+
__name(metaValue2, "metaValue");
|
|
83548
|
+
__name(metaFlag, "metaFlag");
|
|
83549
|
+
__name(resolveAttributePhysical, "resolveAttributePhysical");
|
|
83550
|
+
}
|
|
83551
|
+
});
|
|
83552
|
+
|
|
83553
|
+
// backend/src/services/ai/modelOverviewCache.ts
|
|
83554
|
+
var modelOverviewCache_exports = {};
|
|
83555
|
+
__export(modelOverviewCache_exports, {
|
|
83556
|
+
MODEL_OVERVIEW_TTL_MS: () => MODEL_OVERVIEW_TTL_MS,
|
|
83557
|
+
getModelOverviewCached: () => getModelOverviewCached,
|
|
83558
|
+
invalidateModelOverviewCache: () => invalidateModelOverviewCache,
|
|
83559
|
+
subscribeModelOverviewCache: () => subscribeModelOverviewCache
|
|
83560
|
+
});
|
|
83561
|
+
function invalidateModelOverviewCache() {
|
|
83562
|
+
cached2 = null;
|
|
83563
|
+
generation++;
|
|
83564
|
+
}
|
|
83565
|
+
function subscribeModelOverviewCache(projection) {
|
|
83566
|
+
return projection.onInvalidate(() => invalidateModelOverviewCache());
|
|
83567
|
+
}
|
|
83568
|
+
async function getModelOverviewCached(build) {
|
|
83569
|
+
if (cached2 && Date.now() - cached2.builtAt < MODEL_OVERVIEW_TTL_MS) {
|
|
83570
|
+
return cached2.value;
|
|
83571
|
+
}
|
|
83572
|
+
if (!inflight) {
|
|
83573
|
+
const startedAt = generation;
|
|
83574
|
+
inflight = build().then((value) => {
|
|
83575
|
+
if (generation === startedAt) cached2 = { value, builtAt: Date.now() };
|
|
83576
|
+
return value;
|
|
83577
|
+
}).finally(() => {
|
|
83578
|
+
inflight = null;
|
|
83579
|
+
});
|
|
83580
|
+
}
|
|
83581
|
+
return inflight;
|
|
83582
|
+
}
|
|
83583
|
+
var MODEL_OVERVIEW_TTL_MS, cached2, inflight, generation;
|
|
83584
|
+
var init_modelOverviewCache = __esm({
|
|
83585
|
+
"backend/src/services/ai/modelOverviewCache.ts"() {
|
|
83586
|
+
"use strict";
|
|
83587
|
+
MODEL_OVERVIEW_TTL_MS = 6e4;
|
|
83588
|
+
cached2 = null;
|
|
83589
|
+
inflight = null;
|
|
83590
|
+
generation = 0;
|
|
83591
|
+
__name(invalidateModelOverviewCache, "invalidateModelOverviewCache");
|
|
83592
|
+
__name(subscribeModelOverviewCache, "subscribeModelOverviewCache");
|
|
83593
|
+
__name(getModelOverviewCached, "getModelOverviewCached");
|
|
83594
|
+
}
|
|
83595
|
+
});
|
|
83596
|
+
|
|
83467
83597
|
// backend/src/services/systemPromptStore.ts
|
|
83468
83598
|
import { createHash as createHash2 } from "crypto";
|
|
83469
83599
|
function systemPromptDigest(body) {
|
|
@@ -104496,10 +104626,6 @@ var init_aiMermaid = __esm({
|
|
|
104496
104626
|
});
|
|
104497
104627
|
|
|
104498
104628
|
// backend/src/controllers/aiSql.ts
|
|
104499
|
-
function metaValue2(meta3, name21) {
|
|
104500
|
-
const e = meta3?.find((m) => m.name === name21);
|
|
104501
|
-
return e && e.value != null ? String(e.value) : void 0;
|
|
104502
|
-
}
|
|
104503
104629
|
function nameList(names, max = 8) {
|
|
104504
104630
|
if (!names.length) return "";
|
|
104505
104631
|
return names.length <= max ? names.join(", ") : `${names.slice(0, max).join(", ")} +${names.length - max} more`;
|
|
@@ -104541,44 +104667,107 @@ function sqlType(base, v, dialect) {
|
|
|
104541
104667
|
}
|
|
104542
104668
|
}
|
|
104543
104669
|
function columnFor(a, derived, dialect) {
|
|
104544
|
-
const
|
|
104545
|
-
const physType = metaValue2(a.metadata, "physical.dbType");
|
|
104670
|
+
const phys = resolveAttributePhysical(a);
|
|
104546
104671
|
const resolved = resolveBase(String(a.type || "string"), a.validation, derived);
|
|
104547
104672
|
return {
|
|
104548
104673
|
attribute: a.name,
|
|
104549
|
-
column:
|
|
104550
|
-
dbType:
|
|
104674
|
+
column: phys.columnName ?? a.name,
|
|
104675
|
+
dbType: phys.dbType ?? sqlType(resolved.base, resolved.validation, dialect),
|
|
104551
104676
|
nullable: !a.required,
|
|
104552
|
-
primaryKey:
|
|
104553
|
-
...
|
|
104677
|
+
primaryKey: phys.primaryKey,
|
|
104678
|
+
...phys.columnName || phys.dbType ? {} : { physicalMappingMissing: true }
|
|
104554
104679
|
};
|
|
104555
104680
|
}
|
|
104556
104681
|
async function buildSqlSchema(input, services, opts) {
|
|
104557
104682
|
const dialect = input.dialect ?? "generic";
|
|
104558
104683
|
const { listMicroservices: listMicroservices2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
104559
|
-
const
|
|
104684
|
+
const allPkgs = await listMicroservices2();
|
|
104685
|
+
if (input.entityNames !== void 0 && !Array.isArray(input.entityNames)) {
|
|
104686
|
+
return {
|
|
104687
|
+
error: `entityNames must be an array of entity names, e.g. entityNames: ['Order']. Retry with an array, or call searchModel({ query: '<entity or business term>' }) to locate the entity first.`
|
|
104688
|
+
};
|
|
104689
|
+
}
|
|
104690
|
+
const requestedNames = Array.isArray(input.entityNames) ? input.entityNames.map((n) => String(n).trim()).filter(Boolean) : void 0;
|
|
104691
|
+
if (input.packageName && !allPkgs.includes(input.packageName)) {
|
|
104692
|
+
return {
|
|
104693
|
+
error: `Package '${input.packageName}' not found. Known packages: ${nameList(allPkgs, 12) || "none"}. Call searchModel({ query: '<entity or business term>' }) to locate the element, or retry with entityNames: ['<EntityName>'] \u2014 entity names resolve across all packages.`
|
|
104694
|
+
};
|
|
104695
|
+
}
|
|
104560
104696
|
const derivedList = await services.derivedTypes.list().catch(() => []);
|
|
104561
104697
|
const derived = new Map(derivedList.map((d) => [d.name, d]));
|
|
104562
|
-
const
|
|
104563
|
-
const nameByUuid = /* @__PURE__ */ new Map();
|
|
104698
|
+
const entitiesByPkg = /* @__PURE__ */ new Map();
|
|
104564
104699
|
for (const p of allPkgs) {
|
|
104565
|
-
|
|
104700
|
+
entitiesByPkg.set(p, await services.serviceService.getServiceEntities(p).catch(() => []));
|
|
104701
|
+
}
|
|
104702
|
+
const nameByUuid = /* @__PURE__ */ new Map();
|
|
104703
|
+
for (const ents of entitiesByPkg.values()) {
|
|
104704
|
+
for (const e of ents) nameByUuid.set(e.uuid, e.name);
|
|
104705
|
+
}
|
|
104706
|
+
const relsByPkg = /* @__PURE__ */ new Map();
|
|
104707
|
+
const relsFor = /* @__PURE__ */ __name(async (p) => {
|
|
104708
|
+
if (!relsByPkg.has(p)) relsByPkg.set(p, await services.serviceService.getPackageRelationships(p).catch(() => []));
|
|
104709
|
+
return relsByPkg.get(p);
|
|
104710
|
+
}, "relsFor");
|
|
104711
|
+
let pkgs;
|
|
104712
|
+
let include = null;
|
|
104713
|
+
let scope;
|
|
104714
|
+
const unresolved = [];
|
|
104715
|
+
if (requestedNames?.length) {
|
|
104716
|
+
pkgs = allPkgs;
|
|
104717
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
104718
|
+
for (const name21 of requestedNames) {
|
|
104719
|
+
const exact = [];
|
|
104720
|
+
const loose = [];
|
|
104721
|
+
for (const ents of entitiesByPkg.values()) {
|
|
104722
|
+
for (const e of ents) {
|
|
104723
|
+
if (e.name === name21) exact.push(e.uuid);
|
|
104724
|
+
else if (e.name.toLowerCase() === name21.toLowerCase()) loose.push(e.uuid);
|
|
104725
|
+
}
|
|
104726
|
+
}
|
|
104727
|
+
const hits = exact.length ? exact : loose;
|
|
104728
|
+
if (hits.length) hits.forEach((u) => wanted.add(u));
|
|
104729
|
+
else unresolved.push(name21);
|
|
104730
|
+
}
|
|
104731
|
+
if (!wanted.size) {
|
|
104732
|
+
return {
|
|
104733
|
+
error: `None of the requested entities (${requestedNames.join(", ")}) exist in any package. Call searchModel({ query: '<entity or business term>' }) to locate the right entity names, then retry.`
|
|
104734
|
+
};
|
|
104735
|
+
}
|
|
104736
|
+
include = new Set(wanted);
|
|
104737
|
+
for (const p of allPkgs) {
|
|
104738
|
+
for (const r of await relsFor(p)) {
|
|
104739
|
+
if (wanted.has(r.source.entity)) include.add(r.target.entity);
|
|
104740
|
+
if (wanted.has(r.target.entity)) include.add(r.source.entity);
|
|
104741
|
+
}
|
|
104742
|
+
}
|
|
104743
|
+
const resolved = requestedNames.filter((n) => !unresolved.includes(n));
|
|
104744
|
+
scope = `entities: ${resolved.join(", ")} (+directly related)`;
|
|
104745
|
+
} else {
|
|
104746
|
+
pkgs = input.packageName ? [input.packageName] : allPkgs;
|
|
104747
|
+
scope = input.packageName ?? "all packages";
|
|
104566
104748
|
}
|
|
104567
104749
|
const tables = [];
|
|
104568
104750
|
const relationships = [];
|
|
104569
104751
|
let missing = 0;
|
|
104752
|
+
const fallbackTables = [];
|
|
104570
104753
|
for (const p of pkgs) {
|
|
104571
|
-
const entities =
|
|
104754
|
+
const entities = (entitiesByPkg.get(p) ?? []).filter((e) => !include || include.has(e.uuid));
|
|
104572
104755
|
for (const e of entities) {
|
|
104573
104756
|
const schema = metaValue2(e.metadata, "physical.schema") ?? (opts?.defaultSchema?.trim() || void 0);
|
|
104574
104757
|
const table = metaValue2(e.metadata, "physical.tableName") ?? e.name;
|
|
104758
|
+
let entityMissing = 0;
|
|
104575
104759
|
const columns = (e.attributes ?? []).map((a) => {
|
|
104576
104760
|
const c = columnFor(a, derived, dialect);
|
|
104577
|
-
if ("physicalMappingMissing" in c)
|
|
104761
|
+
if ("physicalMappingMissing" in c) entityMissing++;
|
|
104578
104762
|
return c;
|
|
104579
104763
|
});
|
|
104764
|
+
if (entityMissing > 0) {
|
|
104765
|
+
missing += entityMissing;
|
|
104766
|
+
if (!fallbackTables.includes(table)) fallbackTables.push(table);
|
|
104767
|
+
}
|
|
104580
104768
|
tables.push({
|
|
104581
104769
|
entity: e.name,
|
|
104770
|
+
package: p,
|
|
104582
104771
|
table,
|
|
104583
104772
|
...schema ? { schema } : {},
|
|
104584
104773
|
// Ready-to-use name for SQL — schema.table when a schema is known.
|
|
@@ -104586,7 +104775,8 @@ async function buildSqlSchema(input, services, opts) {
|
|
|
104586
104775
|
columns
|
|
104587
104776
|
});
|
|
104588
104777
|
}
|
|
104589
|
-
for (const r of await
|
|
104778
|
+
for (const r of await relsFor(p)) {
|
|
104779
|
+
if (include && !(include.has(r.source.entity) && include.has(r.target.entity))) continue;
|
|
104590
104780
|
const from = nameByUuid.get(r.source.entity);
|
|
104591
104781
|
const to = nameByUuid.get(r.target.entity);
|
|
104592
104782
|
if (!from || !to) continue;
|
|
@@ -104599,7 +104789,6 @@ async function buildSqlSchema(input, services, opts) {
|
|
|
104599
104789
|
});
|
|
104600
104790
|
}
|
|
104601
104791
|
}
|
|
104602
|
-
const scope = input.packageName ?? "all packages";
|
|
104603
104792
|
const tableNames = tables.map((t) => t.entity);
|
|
104604
104793
|
return {
|
|
104605
104794
|
// #tool-summary — concise line, NAMES the tables for the tool card.
|
|
@@ -104609,7 +104798,9 @@ async function buildSqlSchema(input, services, opts) {
|
|
|
104609
104798
|
schemaQualifyTables: !!opts?.schemaQualifyTables,
|
|
104610
104799
|
tables,
|
|
104611
104800
|
relationships,
|
|
104612
|
-
|
|
104801
|
+
...unresolved.length ? { unresolvedEntityNames: unresolved } : {},
|
|
104802
|
+
...fallbackTables.length ? { tablesWithFallbackColumns: fallbackTables } : {},
|
|
104803
|
+
note: "Use these PHYSICAL table/column names and dbTypes when writing SQL. " + (opts?.schemaQualifyTables ? "Schema-qualify every table (use each table's qualifiedName). " : "") + (missing > 0 ? `${missing} column(s) in table(s) ${nameList(fallbackTables, 6)} have no explicit physical mapping \u2014 their column names/dbTypes are derived from the logical model; WARN the user before relying on them.` : "All columns have an explicit physical mapping.") + (unresolved.length ? ` Requested entities not found: ${unresolved.join(", ")} \u2014 call searchModel({ query: '<name>' }) to locate them.` : "")
|
|
104613
104804
|
};
|
|
104614
104805
|
}
|
|
104615
104806
|
var getSqlSchemaInputSchema, getSqlSchemaParameters;
|
|
@@ -104617,18 +104808,20 @@ var init_aiSql = __esm({
|
|
|
104617
104808
|
"backend/src/controllers/aiSql.ts"() {
|
|
104618
104809
|
"use strict";
|
|
104619
104810
|
init_zod();
|
|
104811
|
+
init_physicalMapping();
|
|
104620
104812
|
getSqlSchemaInputSchema = external_exports.object({
|
|
104621
104813
|
packageName: external_exports.string().optional().describe("Scope to one package (omit for the whole model)"),
|
|
104814
|
+
entityNames: external_exports.array(external_exports.string()).optional().describe("PREFERRED scope on large models: the target entity names (resolved across all packages). Their directly-related entities are included automatically so JOINs can be derived."),
|
|
104622
104815
|
dialect: external_exports.enum(["generic", "postgres", "mysql", "mssql", "oracle", "sqlite"]).optional().describe("Target SQL dialect \u2014 tunes fallback column types (default generic)")
|
|
104623
104816
|
});
|
|
104624
104817
|
getSqlSchemaParameters = {
|
|
104625
104818
|
type: "object",
|
|
104626
104819
|
properties: {
|
|
104627
104820
|
packageName: { type: "string", description: "Scope to one package (omit for the whole model)" },
|
|
104821
|
+
entityNames: { type: "array", items: { type: "string" }, description: "PREFERRED scope on large models: target entity names, resolved across all packages; directly-related entities are included automatically for JOINs" },
|
|
104628
104822
|
dialect: { type: "string", enum: ["generic", "postgres", "mysql", "mssql", "oracle", "sqlite"], description: "Target SQL dialect (default generic)" }
|
|
104629
104823
|
}
|
|
104630
104824
|
};
|
|
104631
|
-
__name(metaValue2, "metaValue");
|
|
104632
104825
|
__name(nameList, "nameList");
|
|
104633
104826
|
__name(resolveBase, "resolveBase");
|
|
104634
104827
|
__name(sqlType, "sqlType");
|
|
@@ -116829,16 +117022,15 @@ function buildEntityDetails(entity, packageName) {
|
|
|
116829
117022
|
const tableName = metaValue3(entity.metadata, "physical.tableName");
|
|
116830
117023
|
const schema = metaValue3(entity.metadata, "physical.schema");
|
|
116831
117024
|
const attributes = (entity.attributes ?? []).map((a) => {
|
|
116832
|
-
const
|
|
116833
|
-
const dbType = metaValue3(a.metadata, "physical.dbType");
|
|
117025
|
+
const phys = resolveAttributePhysical(a);
|
|
116834
117026
|
return {
|
|
116835
117027
|
name: a.name,
|
|
116836
117028
|
type: a.type,
|
|
116837
117029
|
description: a.description,
|
|
116838
117030
|
required: a.required,
|
|
116839
|
-
primaryKey:
|
|
117031
|
+
primaryKey: phys.primaryKey,
|
|
116840
117032
|
...a.validation ? { validation: a.validation } : {},
|
|
116841
|
-
...columnName || dbType ? { physical: { ...columnName ? { columnName } : {}, ...dbType ? { dbType } : {} } } : {}
|
|
117033
|
+
...phys.columnName || phys.dbType ? { physical: { ...phys.columnName ? { columnName: phys.columnName } : {}, ...phys.dbType ? { dbType: phys.dbType } : {} } } : {}
|
|
116842
117034
|
};
|
|
116843
117035
|
});
|
|
116844
117036
|
const hasPhysical = !!(tableName || schema) || attributes.some((a) => a.physical);
|
|
@@ -116850,6 +117042,7 @@ function buildEntityDetails(entity, packageName) {
|
|
|
116850
117042
|
// #tool-summary — concise self-describing line for the tool card.
|
|
116851
117043
|
summary: `${entity.name}${packageName ? ` (${packageName})` : ""} \u2014 ${attributes.length} attribute${attributes.length === 1 ? "" : "s"}${extras.length ? ` (${extras.join(", ")})` : ""}`,
|
|
116852
117044
|
name: entity.name,
|
|
117045
|
+
...packageName ? { packageName } : {},
|
|
116853
117046
|
description: entity.description,
|
|
116854
117047
|
stereotype: entity.stereotype,
|
|
116855
117048
|
...tableName || schema ? { physical: { ...tableName ? { tableName } : {}, ...schema ? { schema } : {} } } : {},
|
|
@@ -116858,6 +117051,46 @@ function buildEntityDetails(entity, packageName) {
|
|
|
116858
117051
|
...entity.rules?.length ? { rules: entity.rules.map((r) => ({ name: r.name, description: r.description, severity: r.severity })) } : {}
|
|
116859
117052
|
};
|
|
116860
117053
|
}
|
|
117054
|
+
async function executeGetEntityDetails(args, services) {
|
|
117055
|
+
if (!args?.entityName) return { error: "entityName is required." };
|
|
117056
|
+
const matches = await services.serviceService.findEntityMatches(args.entityName, args.packageName || void 0);
|
|
117057
|
+
if (matches.length === 1) {
|
|
117058
|
+
return buildEntityDetails(matches[0].entity, matches[0].packageName);
|
|
117059
|
+
}
|
|
117060
|
+
if (matches.length > 1) {
|
|
117061
|
+
return {
|
|
117062
|
+
summary: `'${args.entityName}' matches ${matches.length} entities \u2014 specify packageName`,
|
|
117063
|
+
ambiguous: true,
|
|
117064
|
+
candidates: matches.map((m) => ({
|
|
117065
|
+
entityName: m.entity.name,
|
|
117066
|
+
packageName: m.packageName,
|
|
117067
|
+
...m.entity.description ? { description: m.entity.description } : {}
|
|
117068
|
+
})),
|
|
117069
|
+
note: "Several packages define an entity with this name. Call getEntityDetails again with the intended packageName."
|
|
117070
|
+
};
|
|
117071
|
+
}
|
|
117072
|
+
return {
|
|
117073
|
+
error: `Entity '${args.entityName}' not found in ${args.packageName ? `package '${args.packageName}' or ` : ""}any package. Call searchModel({ query: '${args.entityName}' }) to locate it by full-text search \u2014 the model may use a different name.`
|
|
117074
|
+
};
|
|
117075
|
+
}
|
|
117076
|
+
async function executeListEntities(args, services) {
|
|
117077
|
+
const { listMicroservices: listMicroservices2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
117078
|
+
if (args?.packageName) {
|
|
117079
|
+
const packages2 = await listMicroservices2().catch(() => []);
|
|
117080
|
+
if (!packages2.includes(args.packageName)) {
|
|
117081
|
+
return {
|
|
117082
|
+
error: `Package '${args.packageName}' not found. Known packages: ${nameList2(packages2, 12) || "none"}. Call searchModel({ query: '<entity or business term>' }) to locate the element you are after.`
|
|
117083
|
+
};
|
|
117084
|
+
}
|
|
117085
|
+
const entities = await services.serviceService.getServiceEntities(args.packageName);
|
|
117086
|
+
return {
|
|
117087
|
+
summary: `${args.packageName}: ${nameList2(entities.map((e) => e.name)) || "no entities"}`,
|
|
117088
|
+
entities: entities.map((e) => ({ name: e.name, description: e.description, attrCount: e.attributes?.length || 0 }))
|
|
117089
|
+
};
|
|
117090
|
+
}
|
|
117091
|
+
const packages = await listMicroservices2();
|
|
117092
|
+
return { summary: `packages: ${nameList2(packages) || "none"}`, packages };
|
|
117093
|
+
}
|
|
116861
117094
|
async function buildModelOverview(services) {
|
|
116862
117095
|
const { listMicroservices: listMicroservices2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
116863
117096
|
const pkgNames = await listMicroservices2().catch(() => []);
|
|
@@ -116916,9 +117149,35 @@ function formatModelOutline(o) {
|
|
|
116916
117149
|
Packages:
|
|
116917
117150
|
${pkgLines}${extra.length ? "\n" + extra.join("\n") : ""}`;
|
|
116918
117151
|
}
|
|
117152
|
+
function formatModelOutlineWithinBudget(o, maxChars = MODEL_OUTLINE_MAX_CHARS) {
|
|
117153
|
+
const full = formatModelOutline(o);
|
|
117154
|
+
if (full.length <= maxChars) return full;
|
|
117155
|
+
const t = o.totals;
|
|
117156
|
+
const head = `Current model snapshot \u2014 ${t.packages} package(s), ${t.entities} entities, ${t.relationships} relationships, ${t.cases} cases, ${t.rules} rules, ${t.events} events, ${t.actions} actions, ${t.stateMachines} state machines, ${t.derivedTypes} derived types, ${t.stereotypes} stereotypes.`;
|
|
117157
|
+
const banner = `Entity lists omitted (${t.entities} entities across ${t.packages} packages \u2014 too large to inline). You do NOT have the full entity list. To locate any entity, call searchModel with its name or a business term, then getEntityDetails (packageName optional) or getSqlSchema with entityNames. Do NOT guess entity, package, or table names.`;
|
|
117158
|
+
const pkgLines = o.packages.map((p) => ` - ${p.name}: ${p.entities.length} entit${p.entities.length === 1 ? "y" : "ies"}, ${p.relationships} relationship${p.relationships === 1 ? "" : "s"}`);
|
|
117159
|
+
const fixed = head.length + banner.length + "Packages:\n".length + 4;
|
|
117160
|
+
const lines = [];
|
|
117161
|
+
let used = fixed;
|
|
117162
|
+
for (let i = 0; i < pkgLines.length; i++) {
|
|
117163
|
+
const remaining = pkgLines.length - i;
|
|
117164
|
+
if (used + pkgLines[i].length + 1 > maxChars - 80 && remaining > 1) {
|
|
117165
|
+
lines.push(` \u2026 +${remaining} more packages (call listEntities to enumerate)`);
|
|
117166
|
+
break;
|
|
117167
|
+
}
|
|
117168
|
+
lines.push(pkgLines[i]);
|
|
117169
|
+
used += pkgLines[i].length + 1;
|
|
117170
|
+
}
|
|
117171
|
+
return `${head}
|
|
117172
|
+
${banner}
|
|
117173
|
+
Packages:
|
|
117174
|
+
${lines.join("\n")}`;
|
|
117175
|
+
}
|
|
116919
117176
|
async function safeModelOutline(services) {
|
|
116920
117177
|
try {
|
|
116921
|
-
return
|
|
117178
|
+
return formatModelOutlineWithinBudget(
|
|
117179
|
+
await getModelOverviewCached(() => buildModelOverview(services))
|
|
117180
|
+
);
|
|
116922
117181
|
} catch {
|
|
116923
117182
|
return "";
|
|
116924
117183
|
}
|
|
@@ -116930,8 +117189,8 @@ function resolveToolCategory(toolName, trustByName) {
|
|
|
116930
117189
|
const clean = toolName.replace(/^functions\./, "").split(":")[0];
|
|
116931
117190
|
const builtin = TOOL_CATEGORY_MAP[clean];
|
|
116932
117191
|
if (builtin) return builtin;
|
|
116933
|
-
const
|
|
116934
|
-
if (
|
|
117192
|
+
const registered5 = getAgentTool(clean)?.category;
|
|
117193
|
+
if (registered5) return registered5;
|
|
116935
117194
|
if (clean.includes(".")) {
|
|
116936
117195
|
const trust = trustByName.get(clean);
|
|
116937
117196
|
return trust === "auto" ? "read" : "modify";
|
|
@@ -117084,7 +117343,7 @@ function buildSystemPrompt(pageContext, conversationSystemPrompt, mode = "design
|
|
|
117084
117343
|
if (typeof modelOutline === "string" && modelOutline.trim().length > 0) {
|
|
117085
117344
|
out += `
|
|
117086
117345
|
|
|
117087
|
-
${modelOutline.trim().slice(0,
|
|
117346
|
+
${modelOutline.trim().slice(0, MODEL_OUTLINE_MAX_CHARS)}`;
|
|
117088
117347
|
}
|
|
117089
117348
|
if (typeof pageContext === "string") {
|
|
117090
117349
|
const trimmed2 = pageContext.trim();
|
|
@@ -117096,7 +117355,7 @@ Page context: ${trimmed2.slice(0, 500)}`;
|
|
|
117096
117355
|
}
|
|
117097
117356
|
return out;
|
|
117098
117357
|
}
|
|
117099
|
-
function buildDirectChatMessages(rawMessages, maxToolResultChars =
|
|
117358
|
+
function buildDirectChatMessages(rawMessages, maxToolResultChars = DIRECT_TOOL_RESULT_MAX_CHARS) {
|
|
117100
117359
|
const out = [];
|
|
117101
117360
|
for (const msg of rawMessages) {
|
|
117102
117361
|
const text2 = msg.parts?.find((p) => p.type === "text")?.text || msg.content || "";
|
|
@@ -117112,10 +117371,13 @@ function buildDirectChatMessages(rawMessages, maxToolResultChars = 2e3) {
|
|
|
117112
117371
|
}))
|
|
117113
117372
|
});
|
|
117114
117373
|
for (const tc of toolCalls) {
|
|
117374
|
+
const serialized = JSON.stringify(tc.output ?? {});
|
|
117115
117375
|
out.push({
|
|
117116
117376
|
role: "tool",
|
|
117117
117377
|
tool_call_id: String(tc.id),
|
|
117118
|
-
|
|
117378
|
+
// Truncation must be LOUD — a silent cut reads as a complete result
|
|
117379
|
+
// and the model trusts a partial schema instead of narrowing scope.
|
|
117380
|
+
content: serialized.length > maxToolResultChars ? serialized.slice(0, maxToolResultChars) + TOOL_RESULT_TRUNCATION_MARKER : serialized
|
|
117119
117381
|
});
|
|
117120
117382
|
}
|
|
117121
117383
|
} else if (text2) {
|
|
@@ -117170,10 +117432,10 @@ async function handleDirectChat(req, res, cfg, rawMessages, services, pageContex
|
|
|
117170
117432
|
{ type: "function", function: { name: "createAction", description: "Create an Action/command (e.g. PlaceOrder) on an aggregate entity (ownerEntityName), optionally CQRS-classified, with an optional flow of steps. Use flow steps emitEvent {name} and wait {for} to wire a saga/process across actions+events.", parameters: createActionParameters } },
|
|
117171
117433
|
{ type: "function", function: { name: "createStateMachine", description: "Create a state machine on an entity (ownerEntityName) \u2014 its states, initialState, and transitions (from/to/on). Model an entity lifecycle, e.g. Order PENDING\u2192PAID\u2192SHIPPED.", parameters: createStateMachineParameters } },
|
|
117172
117434
|
{ type: "function", function: { name: "listEntities", description: "List packages or entities in a package", parameters: { type: "object", properties: { packageName: { type: "string", description: "Package name (omit to list all)" } } } } },
|
|
117173
|
-
{ type: "function", function: { name: "getEntityDetails", description: "Get full detail for one entity: attributes with type/required/primaryKey AND field validation, the PHYSICAL mapping (entity table name + schema, each attribute physical column name + DB type), physical constraints, and inline business rules. Call this before writing SQL/DDL so you use the real physical names and types.", parameters: { type: "object", required: ["
|
|
117435
|
+
{ type: "function", function: { name: "getEntityDetails", description: "Get full detail for one entity: attributes with type/required/primaryKey AND field validation, the PHYSICAL mapping (entity table name + schema, each attribute physical column name + DB type), physical constraints, and inline business rules. packageName is OPTIONAL \u2014 omit it and the entity name is resolved across every package (ambiguous names return a candidate list). Call this before writing SQL/DDL so you use the real physical names and types.", parameters: { type: "object", required: ["entityName"], properties: { packageName: { type: "string", description: "Owning package if known (optional \u2014 resolved across packages when omitted)" }, entityName: { type: "string" } } } } },
|
|
117174
117436
|
{ type: "function", function: { name: "getModelOverview", description: "Get a whole-model outline: every package with its entity names, and a count of each concept (entities, relationships, cases, rules, events, actions, state machines, derived types, stereotypes). A snapshot is already in your context; call this to refresh after changes.", parameters: { type: "object", properties: {} } } },
|
|
117175
117437
|
{ type: "function", function: { name: "generateMermaid", description: 'Convert the model to Mermaid diagram source. diagram: "er" (entity-relationship of a package, or all), "class" (class diagram), "state" (a single entity state machine \u2014 pass entityName), "flow" (actions+events saga). Returns { mermaid }. Present it inside a ```mermaid code block.', parameters: generateMermaidParameters } },
|
|
117176
|
-
{ type: "function", function: { name: "getSqlSchema", description: "Get the PHYSICAL relational schema for writing SQL: per-entity table name + schema, each column (physical name, dbType, nullable, primaryKey), and relationships as join hints.
|
|
117438
|
+
{ type: "function", function: { name: "getSqlSchema", description: "Get the PHYSICAL relational schema for writing SQL: per-entity table name + schema, each column (physical name, dbType, nullable, primaryKey), and relationships as join hints. PREFER scoping with entityNames (target entities, resolved across packages; directly-related entities are included automatically for JOINs) or packageName \u2014 an unscoped call on a large model returns a huge result that may be truncated. Optional dialect. Call this before writing any SQL query or DDL so you use real physical names/types, not the conceptual (logical) names.", parameters: getSqlSchemaParameters } },
|
|
117177
117439
|
{ type: "function", function: { name: "listStereotypes", description: "List available stereotypes", parameters: { type: "object", properties: {} } } },
|
|
117178
117440
|
{ type: "function", function: { name: "navigateTo", description: 'Navigate user to a page. The path MUST be an absolute URL beginning with "/" that matches one of the patterns returned by listRoutes \u2014 call listRoutes first if you are unsure of the exact shape.', parameters: { type: "object", required: ["path", "reason"], properties: { path: { type: "string" }, reason: { type: "string" } } } } },
|
|
117179
117441
|
{ type: "function", function: { name: "listRoutes", description: "List every valid URL pattern in the app with a short description and (where useful) a concrete example. Call this BEFORE navigateTo if you are unsure of the exact path shape \u2014 e.g. plural vs singular, where attribute pages live, what the case route is.", parameters: { type: "object", properties: {} } } }
|
|
@@ -117235,21 +117497,13 @@ async function handleDirectChat(req, res, cfg, rawMessages, services, pageContex
|
|
|
117235
117497
|
return await executeCreateStateMachine(args, conceptServices);
|
|
117236
117498
|
}
|
|
117237
117499
|
if (name21 === "listEntities") {
|
|
117238
|
-
|
|
117239
|
-
const entities = await services.serviceService.getServiceEntities(args.packageName);
|
|
117240
|
-
return { summary: `${args.packageName}: ${nameList2(entities.map((e) => e.name)) || "no entities"}`, entities: entities.map((e) => ({ name: e.name, description: e.description })) };
|
|
117241
|
-
}
|
|
117242
|
-
const { listMicroservices: listMicroservices2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
117243
|
-
const packages = await listMicroservices2();
|
|
117244
|
-
return { summary: `packages: ${nameList2(packages) || "none"}`, packages };
|
|
117500
|
+
return await executeListEntities(args, services);
|
|
117245
117501
|
}
|
|
117246
117502
|
if (name21 === "getEntityDetails") {
|
|
117247
|
-
|
|
117248
|
-
if (!entity) return { error: "Entity not found" };
|
|
117249
|
-
return buildEntityDetails(entity, args.packageName);
|
|
117503
|
+
return await executeGetEntityDetails(args, services);
|
|
117250
117504
|
}
|
|
117251
117505
|
if (name21 === "getModelOverview") {
|
|
117252
|
-
return await buildModelOverview(services);
|
|
117506
|
+
return await getModelOverviewCached(() => buildModelOverview(services));
|
|
117253
117507
|
}
|
|
117254
117508
|
if (name21 === "generateMermaid") {
|
|
117255
117509
|
return await generateMermaidDiagram(args, services);
|
|
@@ -117288,8 +117542,11 @@ async function handleDirectChat(req, res, cfg, rawMessages, services, pageContex
|
|
|
117288
117542
|
}
|
|
117289
117543
|
}
|
|
117290
117544
|
const out = await runTool(name21, args);
|
|
117291
|
-
if (isGatedCategory(category)
|
|
117292
|
-
|
|
117545
|
+
if (isGatedCategory(category)) {
|
|
117546
|
+
invalidateModelOverviewCache();
|
|
117547
|
+
if (out && typeof out === "object" && out.success === true) {
|
|
117548
|
+
mutatingSuccessCount++;
|
|
117549
|
+
}
|
|
117293
117550
|
}
|
|
117294
117551
|
return out;
|
|
117295
117552
|
}, "executeTool");
|
|
@@ -117415,7 +117672,7 @@ ${lines.join("\n")}` : "";
|
|
|
117415
117672
|
return "";
|
|
117416
117673
|
}
|
|
117417
117674
|
}
|
|
117418
|
-
var TOOL_CATEGORY_MAP, READ_ONLY_TOOLS, MODE_SYSTEM_SUFFIX, GATED_CATEGORIES, DENIED_RESULT, SYSTEM_PROMPT, aiChat, aiChatApprove, aiStatus, aiGetConfig, aiSaveConfig, aiTestTools, aiTools, aiMentionsSearch, listConversations, getConversation, saveConversation, patchConversation, deleteConversation, getSystemPromptByDigest, listPrompts, getPrompt, createPrompt, updatePrompt, deletePrompt;
|
|
117675
|
+
var TOOL_CATEGORY_MAP, READ_ONLY_TOOLS, MODE_SYSTEM_SUFFIX, GATED_CATEGORIES, MODEL_OUTLINE_MAX_CHARS, DENIED_RESULT, SYSTEM_PROMPT, DIRECT_TOOL_RESULT_MAX_CHARS, TOOL_RESULT_TRUNCATION_MARKER, aiChat, aiChatApprove, aiStatus, aiGetConfig, aiSaveConfig, aiTestTools, aiTools, aiMentionsSearch, listConversations, getConversation, saveConversation, patchConversation, deleteConversation, getSystemPromptByDigest, listPrompts, getPrompt, createPrompt, updatePrompt, deletePrompt;
|
|
117419
117676
|
var init_aiController = __esm({
|
|
117420
117677
|
"backend/src/controllers/aiController.ts"() {
|
|
117421
117678
|
"use strict";
|
|
@@ -117425,6 +117682,8 @@ var init_aiController = __esm({
|
|
|
117425
117682
|
init_config();
|
|
117426
117683
|
init_agentToolRegistry();
|
|
117427
117684
|
init_authoringRules();
|
|
117685
|
+
init_physicalMapping();
|
|
117686
|
+
init_modelOverviewCache();
|
|
117428
117687
|
init_systemPromptStore();
|
|
117429
117688
|
init_appDir();
|
|
117430
117689
|
init_conversationService();
|
|
@@ -117496,8 +117755,12 @@ Mode: REVIEW. You are reviewing the data model for quality issues. Use read-only
|
|
|
117496
117755
|
__name(nameList2, "nameList");
|
|
117497
117756
|
__name(metaValue3, "metaValue");
|
|
117498
117757
|
__name(buildEntityDetails, "buildEntityDetails");
|
|
117758
|
+
__name(executeGetEntityDetails, "executeGetEntityDetails");
|
|
117759
|
+
__name(executeListEntities, "executeListEntities");
|
|
117499
117760
|
__name(buildModelOverview, "buildModelOverview");
|
|
117500
117761
|
__name(formatModelOutline, "formatModelOutline");
|
|
117762
|
+
MODEL_OUTLINE_MAX_CHARS = 4e3;
|
|
117763
|
+
__name(formatModelOutlineWithinBudget, "formatModelOutlineWithinBudget");
|
|
117501
117764
|
__name(safeModelOutline, "safeModelOutline");
|
|
117502
117765
|
__name(isGatedCategory, "isGatedCategory");
|
|
117503
117766
|
__name(resolveToolCategory, "resolveToolCategory");
|
|
@@ -117523,21 +117786,24 @@ This system models a RICH domain \u2014 not just entities. You can author all of
|
|
|
117523
117786
|
- Events \u2014 domain events emitted by aggregates, e.g. OrderPlaced (createEvent)
|
|
117524
117787
|
- Actions \u2014 commands/queries on aggregates with a flow; emitEvent/wait steps wire a saga/process (createAction)
|
|
117525
117788
|
- State machines \u2014 entity lifecycles: states + transitions, e.g. Order PENDING\u2192PAID\u2192SHIPPED (createStateMachine)
|
|
117526
|
-
- Read/inspect: searchModel (full-text search across the whole dictionary \u2014 use it to LOCATE an entity/attribute/rule when you don't know its exact name, then drill in), getModelOverview (whole-model outline \u2014 packages, entities, and concept counts; a current snapshot is already shown to you below), getEntityDetails (one entity in full incl. physical mapping), listEntities, listStereotypes; navigate with navigateTo (call listRoutes first if unsure)
|
|
117789
|
+
- Read/inspect: searchModel (full-text search across the whole dictionary \u2014 use it to LOCATE an entity/attribute/rule when you don't know its exact name, then drill in), getModelOverview (whole-model outline \u2014 packages, entities, and concept counts; a current snapshot is already shown to you below), getEntityDetails (one entity in full incl. physical mapping; packageName optional \u2014 the name resolves across packages), listEntities, listStereotypes; navigate with navigateTo (call listRoutes first if unsure)
|
|
117527
117790
|
- Diagram: generateMermaid converts the model to Mermaid source \u2014 diagram: "er" (entity-relationship of a package/all), "class" (class diagram), "state" (an entity's state machine, needs entityName), or "flow" (actions+events saga). Use it when the user asks for a diagram, ERD, or visualization, and present the result in a fenced mermaid code block.
|
|
117528
117791
|
|
|
117529
117792
|
A "process" or "saga" is NOT a separate object \u2014 it is the graph that emerges from Actions (with emitEvent/wait flow steps) and Events. To model a process, create the Actions and Events; the saga view is derived automatically.
|
|
117530
117793
|
|
|
117531
117794
|
CONCEPTUAL vs PHYSICAL model \u2014 keep these two layers distinct:
|
|
117532
117795
|
- CONCEPTUAL / LOGICAL is the business model: entity names (PascalCase, e.g. Order), attribute names (camelCase, e.g. orderNumber), and logical or derived types (e.g. money, email, currency-code). This is what you author and what users discuss.
|
|
117533
|
-
- PHYSICAL is how it is persisted in a database: a table name (physical.tableName) in a schema, per-column physical names (physical.columnName) and DB types (physical.dbType), plus constraints. The two layers can differ completely (logical Order.orderNumber \u2192 physical orders.order_no VARCHAR). getEntityDetails returns the physical mapping for one entity; getSqlSchema returns the
|
|
117796
|
+
- PHYSICAL is how it is persisted in a database: a table name (physical.tableName) in a schema, per-column physical names (physical.columnName) and DB types (physical.dbType), plus constraints. The two layers can differ completely (logical Order.orderNumber \u2192 physical orders.order_no VARCHAR). getEntityDetails returns the physical mapping for one entity; getSqlSchema returns the physical relational schema (scope it with entityNames or packageName on anything but a small model).
|
|
117534
117797
|
- A logical/derived type (money, email) is NOT a DB type \u2014 it maps to a physical dbType (money \u2192 DECIMAL(12,2), email \u2192 VARCHAR(254)).
|
|
117535
117798
|
- NEVER write DDL or SQL using conceptual names. Always resolve to the physical table/column names and dbTypes first. If an element has no physical mapping yet, getSqlSchema returns a fallback dbType derived from the logical type and flags it \u2014 pass that flag on to the user.
|
|
117536
117799
|
|
|
117537
117800
|
Generating database queries: when the user asks for a SQL query (SELECT / INSERT / UPDATE / DELETE), a report, or DDL \u2014
|
|
117538
|
-
1.
|
|
117539
|
-
2.
|
|
117540
|
-
3.
|
|
117801
|
+
1. LOCATE the entity first. Unless you already know its owning package (from the snapshot, page context, or an @mention), call searchModel with the entity name or business term to resolve entity \u2192 package. NEVER guess a package, entity, or table name \u2014 the snapshot above may not list every entity.
|
|
117802
|
+
2. Call getSqlSchema scoped NARROWLY: pass entityNames: [...] for the target entities (their directly-related entities are included automatically so you can derive JOINs), or a packageName; pass the target dialect. Do not call it unscoped on a large model \u2014 the result gets truncated and misleads you.
|
|
117803
|
+
3. Write the query using ONLY the physical names returned by the tool; derive JOINs from the relationships (join the PK of the "one" side to the FK on the "many" side) and respect the requested dialect.
|
|
117804
|
+
4. If any column is flagged physicalMappingMissing, tell the user those column names/types are unverified fallbacks derived from the logical model \u2014 do not silently pass them off as real.
|
|
117805
|
+
5. Present the query in a fenced sql code block, and briefly note any assumption (an inferred join column, a missing physical mapping). You generate queries for the user to run \u2014 you do NOT execute them against a live database.
|
|
117806
|
+
If a lookup tool returns an error or an empty result, do not retry the same guess \u2014 follow the error's guidance (usually: call searchModel), or ask the user.
|
|
117541
117807
|
|
|
117542
117808
|
When creating data models:
|
|
117543
117809
|
- Use meaningful names (PascalCase for entities/events, camelCase for attributes)
|
|
@@ -117568,6 +117834,8 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
117568
117834
|
__name(sqlSettingsInstruction, "sqlSettingsInstruction");
|
|
117569
117835
|
__name(standingSystemPrompt, "standingSystemPrompt");
|
|
117570
117836
|
__name(buildSystemPrompt, "buildSystemPrompt");
|
|
117837
|
+
DIRECT_TOOL_RESULT_MAX_CHARS = 2e4;
|
|
117838
|
+
TOOL_RESULT_TRUNCATION_MARKER = "\u2026[truncated \u2014 result too large; retry with a narrower scope, e.g. packageName or entityNames filter]";
|
|
117571
117839
|
__name(buildDirectChatMessages, "buildDirectChatMessages");
|
|
117572
117840
|
__name(buildUiMessagesWithToolParts, "buildUiMessagesWithToolParts");
|
|
117573
117841
|
__name(handleDirectChat, "handleDirectChat");
|
|
@@ -117620,7 +117888,11 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
117620
117888
|
const decision = await awaitApproval(streamId, toolCallId);
|
|
117621
117889
|
if (decision === "deny") return { ...DENIED_RESULT };
|
|
117622
117890
|
}
|
|
117623
|
-
|
|
117891
|
+
try {
|
|
117892
|
+
return await run();
|
|
117893
|
+
} finally {
|
|
117894
|
+
if (isGatedCategory(category)) invalidateModelOverviewCache();
|
|
117895
|
+
}
|
|
117624
117896
|
}, "gate");
|
|
117625
117897
|
const mcpToolEntries = {};
|
|
117626
117898
|
for (const mcpTool of mcpTools) {
|
|
@@ -117791,29 +118063,21 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
117791
118063
|
}),
|
|
117792
118064
|
execute: /* @__PURE__ */ __name(async (params) => {
|
|
117793
118065
|
try {
|
|
117794
|
-
|
|
117795
|
-
const entities = await services.serviceService.getServiceEntities(params.packageName || "default");
|
|
117796
|
-
return { summary: `${params.packageName}: ${nameList2(entities.map((e) => e.name)) || "no entities"}`, entities: entities.map((e) => ({ name: e.name, description: e.description, attrCount: e.attributes?.length || 0 })) };
|
|
117797
|
-
}
|
|
117798
|
-
const { listMicroservices: listMicroservices2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
117799
|
-
const packages = await listMicroservices2();
|
|
117800
|
-
return { summary: `packages: ${nameList2(packages) || "none"}`, packages };
|
|
118066
|
+
return await executeListEntities(params, services);
|
|
117801
118067
|
} catch (err) {
|
|
117802
118068
|
return { error: err.message };
|
|
117803
118069
|
}
|
|
117804
118070
|
}, "execute")
|
|
117805
118071
|
}),
|
|
117806
118072
|
getEntityDetails: tool({
|
|
117807
|
-
description: "Get full detail for an entity: attributes with type/required/primaryKey AND field validation, the PHYSICAL mapping (entity table name + schema, each attribute physical column name + DB type), physical constraints, and inline business rules. Use this before writing SQL/DDL so you use the real physical names and types.",
|
|
118073
|
+
description: "Get full detail for an entity: attributes with type/required/primaryKey AND field validation, the PHYSICAL mapping (entity table name + schema, each attribute physical column name + DB type), physical constraints, and inline business rules. packageName is OPTIONAL \u2014 omit it and the entity name is resolved across every package (ambiguous names return a candidate list). Use this before writing SQL/DDL so you use the real physical names and types.",
|
|
117808
118074
|
inputSchema: external_exports.object({
|
|
117809
|
-
packageName: external_exports.string(),
|
|
118075
|
+
packageName: external_exports.string().optional().describe("Owning package if known (optional \u2014 resolved across packages when omitted)"),
|
|
117810
118076
|
entityName: external_exports.string()
|
|
117811
118077
|
}),
|
|
117812
118078
|
execute: /* @__PURE__ */ __name(async (params) => {
|
|
117813
118079
|
try {
|
|
117814
|
-
|
|
117815
|
-
if (!entity) return { error: "Entity not found" };
|
|
117816
|
-
return buildEntityDetails(entity, params.packageName);
|
|
118080
|
+
return await executeGetEntityDetails(params, services);
|
|
117817
118081
|
} catch (err) {
|
|
117818
118082
|
return { error: err.message };
|
|
117819
118083
|
}
|
|
@@ -117824,7 +118088,7 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
117824
118088
|
inputSchema: external_exports.object({}),
|
|
117825
118089
|
execute: /* @__PURE__ */ __name(async () => {
|
|
117826
118090
|
try {
|
|
117827
|
-
return await buildModelOverview(services);
|
|
118091
|
+
return await getModelOverviewCached(() => buildModelOverview(services));
|
|
117828
118092
|
} catch (err) {
|
|
117829
118093
|
return { error: err.message };
|
|
117830
118094
|
}
|
|
@@ -117842,7 +118106,7 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
117842
118106
|
}, "execute")
|
|
117843
118107
|
}),
|
|
117844
118108
|
getSqlSchema: tool({
|
|
117845
|
-
description: "Get the PHYSICAL relational schema for writing SQL: per-entity table name + schema, each column (physical name, dbType, nullable, primaryKey), and relationships as join hints.
|
|
118109
|
+
description: "Get the PHYSICAL relational schema for writing SQL: per-entity table name + schema, each column (physical name, dbType, nullable, primaryKey), and relationships as join hints. PREFER scoping with entityNames (target entities, resolved across packages; directly-related entities are included automatically for JOINs) or packageName \u2014 an unscoped call on a large model returns a huge result that may be truncated. Optional dialect. Call this before writing any SQL query or DDL so you use real physical names/types, not the conceptual (logical) names.",
|
|
117846
118110
|
inputSchema: getSqlSchemaInputSchema,
|
|
117847
118111
|
execute: /* @__PURE__ */ __name(async (params) => {
|
|
117848
118112
|
try {
|
|
@@ -118275,7 +118539,7 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
118275
118539
|
description: "Get full entity detail: attributes + validation, the physical mapping (table/schema, column names, DB types), constraints, and inline rules. Use before writing SQL/DDL.",
|
|
118276
118540
|
source: "builtin",
|
|
118277
118541
|
parameters: [
|
|
118278
|
-
{ name: "packageName", type: "string", required:
|
|
118542
|
+
{ name: "packageName", type: "string", required: false, description: "Owning package if known (omit to resolve the entity name across all packages)" },
|
|
118279
118543
|
{ name: "entityName", type: "string", required: true, description: "Entity name" }
|
|
118280
118544
|
]
|
|
118281
118545
|
},
|
|
@@ -118297,10 +118561,11 @@ Be concise in your responses. Show a summary of what you created.`;
|
|
|
118297
118561
|
},
|
|
118298
118562
|
{
|
|
118299
118563
|
name: "getSqlSchema",
|
|
118300
|
-
description: "Physical relational schema for writing SQL: tables, columns (physical name + dbType + nullable + PK), and relationships as join hints. Use before generating any SQL query/DDL.",
|
|
118564
|
+
description: "Physical relational schema for writing SQL: tables, columns (physical name + dbType + nullable + PK), and relationships as join hints. Use before generating any SQL query/DDL. Prefer scoping with entityNames or packageName on large models.",
|
|
118301
118565
|
source: "builtin",
|
|
118302
118566
|
parameters: [
|
|
118303
118567
|
{ name: "packageName", type: "string", required: false, description: "Scope to one package (omit for all)" },
|
|
118568
|
+
{ name: "entityNames", type: "string[]", required: false, description: "Preferred scope: target entity names, resolved across packages; directly-related entities are included automatically for JOINs" },
|
|
118304
118569
|
{ name: "dialect", type: "generic|postgres|mysql|mssql|oracle|sqlite", required: false, description: "Target SQL dialect (default generic)" }
|
|
118305
118570
|
]
|
|
118306
118571
|
},
|
|
@@ -150095,8 +150360,8 @@ var require_inflight = __commonJS({
|
|
|
150095
150360
|
var wrappy = require_wrappy();
|
|
150096
150361
|
var reqs = /* @__PURE__ */ Object.create(null);
|
|
150097
150362
|
var once = require_once();
|
|
150098
|
-
module.exports = wrappy(
|
|
150099
|
-
function
|
|
150363
|
+
module.exports = wrappy(inflight2);
|
|
150364
|
+
function inflight2(key2, cb) {
|
|
150100
150365
|
if (reqs[key2]) {
|
|
150101
150366
|
reqs[key2].push(cb);
|
|
150102
150367
|
return null;
|
|
@@ -150105,7 +150370,7 @@ var require_inflight = __commonJS({
|
|
|
150105
150370
|
return makeres(key2);
|
|
150106
150371
|
}
|
|
150107
150372
|
}
|
|
150108
|
-
__name(
|
|
150373
|
+
__name(inflight2, "inflight");
|
|
150109
150374
|
function makeres(key2) {
|
|
150110
150375
|
return once(/* @__PURE__ */ __name(function RES() {
|
|
150111
150376
|
var cbs = reqs[key2];
|
|
@@ -150157,7 +150422,7 @@ var require_glob = __commonJS({
|
|
|
150157
150422
|
var alphasorti = common.alphasorti;
|
|
150158
150423
|
var setopts = common.setopts;
|
|
150159
150424
|
var ownProp = common.ownProp;
|
|
150160
|
-
var
|
|
150425
|
+
var inflight2 = require_inflight();
|
|
150161
150426
|
var util2 = __require("util");
|
|
150162
150427
|
var childrenIgnored = common.childrenIgnored;
|
|
150163
150428
|
var isIgnored = common.isIgnored;
|
|
@@ -150489,7 +150754,7 @@ var require_glob = __commonJS({
|
|
|
150489
150754
|
return this._readdir(abs, false, cb);
|
|
150490
150755
|
var lstatkey = "lstat\0" + abs;
|
|
150491
150756
|
var self2 = this;
|
|
150492
|
-
var lstatcb =
|
|
150757
|
+
var lstatcb = inflight2(lstatkey, lstatcb_);
|
|
150493
150758
|
if (lstatcb)
|
|
150494
150759
|
fs16.lstat(abs, lstatcb);
|
|
150495
150760
|
function lstatcb_(er, lstat) {
|
|
@@ -150508,7 +150773,7 @@ var require_glob = __commonJS({
|
|
|
150508
150773
|
Glob.prototype._readdir = function(abs, inGlobStar, cb) {
|
|
150509
150774
|
if (this.aborted)
|
|
150510
150775
|
return;
|
|
150511
|
-
cb =
|
|
150776
|
+
cb = inflight2("readdir\0" + abs + "\0" + inGlobStar, cb);
|
|
150512
150777
|
if (!cb)
|
|
150513
150778
|
return;
|
|
150514
150779
|
if (inGlobStar && !ownProp(this.symlinks, abs))
|
|
@@ -150666,7 +150931,7 @@ var require_glob = __commonJS({
|
|
|
150666
150931
|
}
|
|
150667
150932
|
}
|
|
150668
150933
|
var self2 = this;
|
|
150669
|
-
var statcb =
|
|
150934
|
+
var statcb = inflight2("stat\0" + abs, lstatcb_);
|
|
150670
150935
|
if (statcb)
|
|
150671
150936
|
fs16.lstat(abs, statcb);
|
|
150672
150937
|
function lstatcb_(er, lstat) {
|
|
@@ -159537,6 +159802,24 @@ var setEntityHidden = /* @__PURE__ */ __name(async (req, res) => {
|
|
|
159537
159802
|
res.status(500).json({ message: "Error setting entity visibility", error: error48 });
|
|
159538
159803
|
}
|
|
159539
159804
|
}, "setEntityHidden");
|
|
159805
|
+
var moveEntity = /* @__PURE__ */ __name(async (req, res) => {
|
|
159806
|
+
try {
|
|
159807
|
+
const { service, entity } = req.params;
|
|
159808
|
+
const targetPackage = typeof req.body?.targetPackage === "string" ? req.body.targetPackage : "";
|
|
159809
|
+
if (!targetPackage) {
|
|
159810
|
+
return res.status(400).json({ message: "targetPackage is required" });
|
|
159811
|
+
}
|
|
159812
|
+
const result = await serviceService.moveEntity(service, entity, targetPackage);
|
|
159813
|
+
if (!result.success) {
|
|
159814
|
+
const notFound = result.errors.some((e) => e.includes("not found"));
|
|
159815
|
+
return res.status(notFound ? 404 : 400).json({ message: "Failed to move entity", errors: result.errors });
|
|
159816
|
+
}
|
|
159817
|
+
res.json({ message: "Entity moved", data: { service, entity, targetPackage } });
|
|
159818
|
+
} catch (error48) {
|
|
159819
|
+
logger.error(`Error moving entity: ${error48}`);
|
|
159820
|
+
res.status(500).json({ message: "Error moving entity", error: error48 });
|
|
159821
|
+
}
|
|
159822
|
+
}, "moveEntity");
|
|
159540
159823
|
var setEntityStyle = /* @__PURE__ */ __name(async (req, res) => {
|
|
159541
159824
|
try {
|
|
159542
159825
|
const { service, entity } = req.params;
|
|
@@ -159763,7 +160046,46 @@ var deleteRelationship = /* @__PURE__ */ __name(async (req, res) => {
|
|
|
159763
160046
|
}
|
|
159764
160047
|
}, "deleteRelationship");
|
|
159765
160048
|
|
|
160049
|
+
// backend/src/services/entityMove/agentTools.ts
|
|
160050
|
+
init_zod();
|
|
160051
|
+
init_agentToolRegistry();
|
|
160052
|
+
init_serviceService();
|
|
160053
|
+
var registered = false;
|
|
160054
|
+
function registerMoveEntityAgentTool() {
|
|
160055
|
+
if (registered) return;
|
|
160056
|
+
registered = true;
|
|
160057
|
+
registerAgentTool({
|
|
160058
|
+
name: "moveEntity",
|
|
160059
|
+
category: "modify",
|
|
160060
|
+
description: "Move an entity from one package to another. The entity keeps its UUID, so relationships (cross-package is first-class), cases and diagrams that reference it keep resolving \u2014 nothing is orphaned. Fails if the target package doesn't exist or already has an entity with the same name.",
|
|
160061
|
+
jsonSchema: {
|
|
160062
|
+
type: "object",
|
|
160063
|
+
required: ["sourcePackage", "entityName", "targetPackage"],
|
|
160064
|
+
properties: {
|
|
160065
|
+
sourcePackage: { type: "string", description: "Package the entity currently lives in" },
|
|
160066
|
+
entityName: { type: "string", description: "Name of the entity to move" },
|
|
160067
|
+
targetPackage: { type: "string", description: "Package to move the entity into" }
|
|
160068
|
+
}
|
|
160069
|
+
},
|
|
160070
|
+
inputSchema: external_exports.object({
|
|
160071
|
+
sourcePackage: external_exports.string(),
|
|
160072
|
+
entityName: external_exports.string(),
|
|
160073
|
+
targetPackage: external_exports.string()
|
|
160074
|
+
}),
|
|
160075
|
+
execute: /* @__PURE__ */ __name(async (args) => {
|
|
160076
|
+
const r = await serviceService.moveEntity(
|
|
160077
|
+
String(args.sourcePackage),
|
|
160078
|
+
String(args.entityName),
|
|
160079
|
+
String(args.targetPackage)
|
|
160080
|
+
);
|
|
160081
|
+
return r.success ? { success: true, entity: args.entityName, from: args.sourcePackage, to: args.targetPackage } : { success: false, errors: r.errors };
|
|
160082
|
+
}, "execute")
|
|
160083
|
+
});
|
|
160084
|
+
}
|
|
160085
|
+
__name(registerMoveEntityAgentTool, "registerMoveEntityAgentTool");
|
|
160086
|
+
|
|
159766
160087
|
// backend/src/routes/data-dictionary/entity.routes.ts
|
|
160088
|
+
registerMoveEntityAgentTool();
|
|
159767
160089
|
var router3 = (0, import_express4.Router)();
|
|
159768
160090
|
router3.get("/api/entities/:microservice/:entityName/attributes", getEntityAttributes);
|
|
159769
160091
|
router3.get("/api/entities/:microservice/:entityName/related", getRelatedEntities);
|
|
@@ -159777,6 +160099,7 @@ router3.put("/api/services/:service/entities/:entity", authorizeJwt(["admin" /*
|
|
|
159777
160099
|
router3.delete("/api/services/:service/entities/:entity", authorizeJwt(["admin" /* ADMIN */]), deleteEntity);
|
|
159778
160100
|
router3.put("/api/services/:service/entities/:entity/hidden", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), setEntityHidden);
|
|
159779
160101
|
router3.put("/api/services/:service/entities/:entity/style", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), setEntityStyle);
|
|
160102
|
+
router3.put("/api/services/:service/entities/:entity/move", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), moveEntity);
|
|
159780
160103
|
router3.post("/api/services/:service/entities/:entity/submit", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), submitEntity);
|
|
159781
160104
|
router3.post("/api/services/:service/entities/:entity/approve", authorizeJwt(["admin" /* ADMIN */]), approveEntity);
|
|
159782
160105
|
router3.post("/api/services/:service/entities/:entity/return", authorizeJwt(["admin" /* ADMIN */]), returnEntity);
|
|
@@ -160321,10 +160644,10 @@ init_zod();
|
|
|
160321
160644
|
init_agentToolRegistry();
|
|
160322
160645
|
init_dicoConfigService();
|
|
160323
160646
|
init_serviceService();
|
|
160324
|
-
var
|
|
160647
|
+
var registered2 = false;
|
|
160325
160648
|
function registerElementStyleAgentTools() {
|
|
160326
|
-
if (
|
|
160327
|
-
|
|
160649
|
+
if (registered2) return;
|
|
160650
|
+
registered2 = true;
|
|
160328
160651
|
registerAgentTool({
|
|
160329
160652
|
name: "defineElementStyle",
|
|
160330
160653
|
category: "modify",
|
|
@@ -165089,10 +165412,10 @@ init_zod();
|
|
|
165089
165412
|
init_agentToolRegistry();
|
|
165090
165413
|
init_searchIndexService();
|
|
165091
165414
|
var KINDS = ["entity", "attribute", "package", "relationship", "rule", "metadata", "case"];
|
|
165092
|
-
var
|
|
165415
|
+
var registered3 = false;
|
|
165093
165416
|
function registerSearchAgentTools() {
|
|
165094
|
-
if (
|
|
165095
|
-
|
|
165417
|
+
if (registered3) return;
|
|
165418
|
+
registered3 = true;
|
|
165096
165419
|
registerAgentTool({
|
|
165097
165420
|
name: "searchModel",
|
|
165098
165421
|
category: "read",
|
|
@@ -173535,10 +173858,10 @@ function getSynthesisBrief(dataDir, entityName) {
|
|
|
173535
173858
|
__name(getSynthesisBrief, "getSynthesisBrief");
|
|
173536
173859
|
|
|
173537
173860
|
// backend/src/services/reverseEngineer/agentTools.ts
|
|
173538
|
-
var
|
|
173861
|
+
var registered4 = false;
|
|
173539
173862
|
function registerReverseEngineerAgentTools() {
|
|
173540
|
-
if (
|
|
173541
|
-
|
|
173863
|
+
if (registered4) return;
|
|
173864
|
+
registered4 = true;
|
|
173542
173865
|
registerAgentTool({
|
|
173543
173866
|
name: "listSynthesisBriefs",
|
|
173544
173867
|
category: "read",
|
|
@@ -174207,6 +174530,12 @@ async function mountFrameworkRoutes() {
|
|
|
174207
174530
|
} catch (searchError) {
|
|
174208
174531
|
logger.warn(`SearchIndex initialization failed: ${searchError instanceof Error ? searchError.message : String(searchError)}`);
|
|
174209
174532
|
}
|
|
174533
|
+
try {
|
|
174534
|
+
const { subscribeModelOverviewCache: subscribeModelOverviewCache2 } = await Promise.resolve().then(() => (init_modelOverviewCache(), modelOverviewCache_exports));
|
|
174535
|
+
subscribeModelOverviewCache2(projection);
|
|
174536
|
+
} catch (cacheError) {
|
|
174537
|
+
logger.warn(`ModelOverviewCache subscription failed: ${cacheError instanceof Error ? cacheError.message : String(cacheError)}`);
|
|
174538
|
+
}
|
|
174210
174539
|
} catch (e) {
|
|
174211
174540
|
logger.warn(`Projection/UuidIndex initialization failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
174212
174541
|
}
|