@hamak/smart-data-dico 1.19.0 → 1.20.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.
Files changed (98) hide show
  1. package/backend/dist/server.mjs +544 -440
  2. package/frontend/dist/assets/{abnfDiagram-VRR7QNED-5bf36645.js → abnfDiagram-VRR7QNED-6f198720.js} +1 -1
  3. package/frontend/dist/assets/{arc-77b99720.js → arc-54f6dc83.js} +1 -1
  4. package/frontend/dist/assets/architecture-TIHT7OUA-8222218c.js +1 -0
  5. package/frontend/dist/assets/{architectureDiagram-ZJ3FMSHR-842d4b7e.js → architectureDiagram-ZJ3FMSHR-6dabc0f0.js} +1 -1
  6. package/frontend/dist/assets/{blockDiagram-677ZJIJ3-ce2d6065.js → blockDiagram-677ZJIJ3-90ab5147.js} +1 -1
  7. package/frontend/dist/assets/{c4Diagram-LMCZKHZV-f6f33075.js → c4Diagram-LMCZKHZV-b5a971e3.js} +1 -1
  8. package/frontend/dist/assets/channel-98106291.js +1 -0
  9. package/frontend/dist/assets/{chunk-2Q5K7J3B-e048f182.js → chunk-2Q5K7J3B-ac60a5fb.js} +1 -1
  10. package/frontend/dist/assets/{chunk-32BRIVSS-9f8a0696.js → chunk-32BRIVSS-ddb07474.js} +1 -1
  11. package/frontend/dist/assets/{chunk-5VM5RSS4-8207b825.js → chunk-5VM5RSS4-2db6a06c.js} +1 -1
  12. package/frontend/dist/assets/{chunk-EX3LRPZG-4964b359.js → chunk-EX3LRPZG-baa20613.js} +1 -1
  13. package/frontend/dist/assets/{chunk-JWPE2WC7-8cf4213d.js → chunk-JWPE2WC7-18647660.js} +1 -1
  14. package/frontend/dist/assets/{chunk-MOJQB5TN-7c50afe9.js → chunk-MOJQB5TN-dc09b962.js} +1 -1
  15. package/frontend/dist/assets/{chunk-PUDLZKDR-49760488.js → chunk-PUDLZKDR-6287f125.js} +1 -1
  16. package/frontend/dist/assets/{chunk-RYQCIY6F-0690c709.js → chunk-RYQCIY6F-9786102e.js} +1 -1
  17. package/frontend/dist/assets/{chunk-V7JOEXUC-4c92f24d.js → chunk-V7JOEXUC-158db19a.js} +1 -1
  18. package/frontend/dist/assets/{chunk-VR4S4FIN-73477ca7.js → chunk-VR4S4FIN-8a5a7523.js} +1 -1
  19. package/frontend/dist/assets/{chunk-XXDRQBXY-9faa344e.js → chunk-XXDRQBXY-f0aeb3a2.js} +1 -1
  20. package/frontend/dist/assets/classDiagram-OUVF2IWQ-6fddb962.js +1 -0
  21. package/frontend/dist/assets/classDiagram-v2-EOCWNBFH-6fddb962.js +1 -0
  22. package/frontend/dist/assets/{cose-bilkent-JH36ORCC-eca50abe.js → cose-bilkent-JH36ORCC-97aedf6d.js} +1 -1
  23. package/frontend/dist/assets/cynefin-VYW2F7L2-39a09b7c.js +1 -0
  24. package/frontend/dist/assets/{cynefinDiagram-TSTJHNR4-9daa8c60.js → cynefinDiagram-TSTJHNR4-7414b003.js} +1 -1
  25. package/frontend/dist/assets/{dagre-VKFMJZFB-8cadf293.js → dagre-VKFMJZFB-22f0ea2d.js} +1 -1
  26. package/frontend/dist/assets/{diagram-FQU43EPY-5e4d3a2a.js → diagram-FQU43EPY-54e670d8.js} +1 -1
  27. package/frontend/dist/assets/{diagram-G47NLZAW-07630f46.js → diagram-G47NLZAW-41216f63.js} +1 -1
  28. package/frontend/dist/assets/{diagram-NH7WQ7WH-58c6af5e.js → diagram-NH7WQ7WH-2de1d465.js} +1 -1
  29. package/frontend/dist/assets/{diagram-OA4YK3LP-b0124115.js → diagram-OA4YK3LP-4be5e2c2.js} +1 -1
  30. package/frontend/dist/assets/{diagram-WEI45ONY-f942b2f7.js → diagram-WEI45ONY-0d1d5dd3.js} +1 -1
  31. package/frontend/dist/assets/{ebnfDiagram-CCIWWBDH-42fc377c.js → ebnfDiagram-CCIWWBDH-235afe69.js} +1 -1
  32. package/frontend/dist/assets/{erDiagram-Q63AITRT-46209ae5.js → erDiagram-Q63AITRT-bfbc0736.js} +1 -1
  33. package/frontend/dist/assets/eventmodeling-45OFAUF4-c1d13b17.js +1 -0
  34. package/frontend/dist/assets/flowDiagram-23GEKE2U-50c416a3.js +1 -0
  35. package/frontend/dist/assets/{ganttDiagram-NO4QXBWP-e368e881.js → ganttDiagram-NO4QXBWP-d767d5bd.js} +1 -1
  36. package/frontend/dist/assets/gitGraph-TEB2WS4Q-53567b0e.js +1 -0
  37. package/frontend/dist/assets/{gitGraphDiagram-IHSO6WYX-245b8d31.js → gitGraphDiagram-IHSO6WYX-0af7483d.js} +1 -1
  38. package/frontend/dist/assets/{index-419b7a82.css → index-017e890a.css} +1 -1
  39. package/frontend/dist/assets/index-25c4649a.js +574 -0
  40. package/frontend/dist/assets/info-DKCQHKI2-dfc9537f.js +1 -0
  41. package/frontend/dist/assets/{infoDiagram-FWYZ7A6U-af51f32a.js → infoDiagram-FWYZ7A6U-703eef1c.js} +1 -1
  42. package/frontend/dist/assets/{ishikawaDiagram-FXEZZL3T-9f2be8c6.js → ishikawaDiagram-FXEZZL3T-b6e22c6c.js} +1 -1
  43. package/frontend/dist/assets/{journeyDiagram-5HDEW3XC-1e5b0d35.js → journeyDiagram-5HDEW3XC-7ff9040e.js} +1 -1
  44. package/frontend/dist/assets/{kanban-definition-HUTT4EX6-89d91f2e.js → kanban-definition-HUTT4EX6-3ec9df2c.js} +1 -1
  45. package/frontend/dist/assets/{linear-5deaa3d1.js → linear-a840de21.js} +1 -1
  46. package/frontend/dist/assets/{mermaid-parser.core-a7dd4921.js → mermaid-parser.core-35fa1177.js} +2 -2
  47. package/frontend/dist/assets/{mermaid.core-d2196c74.js → mermaid.core-33299d26.js} +3 -3
  48. package/frontend/dist/assets/{mindmap-definition-LN4V7U3C-36c63353.js → mindmap-definition-LN4V7U3C-29c921b2.js} +1 -1
  49. package/frontend/dist/assets/packet-7NZHBO7P-e3add55d.js +1 -0
  50. package/frontend/dist/assets/{pegDiagram-2B236MQR-30a8ce0f.js → pegDiagram-2B236MQR-c9fd64ea.js} +1 -1
  51. package/frontend/dist/assets/pie-RZYD4A2V-3cdcefc3.js +1 -0
  52. package/frontend/dist/assets/{pieDiagram-ENE6RG2P-639c88ff.js → pieDiagram-ENE6RG2P-d21bc176.js} +1 -1
  53. package/frontend/dist/assets/{quadrantDiagram-ABIIQ3AL-6ff9cdb3.js → quadrantDiagram-ABIIQ3AL-b4dc829d.js} +1 -1
  54. package/frontend/dist/assets/radar-I7S5WNFK-29afeb7a.js +1 -0
  55. package/frontend/dist/assets/railroad-3IZDKUUU-6e6a0bb5.js +1 -0
  56. package/frontend/dist/assets/railroad-abnf-AHOZXSZD-8e00f421.js +1 -0
  57. package/frontend/dist/assets/railroad-ebnf-EBAXGLYW-89a4f0dc.js +1 -0
  58. package/frontend/dist/assets/railroad-peg-LSFZ7HO6-b2782816.js +1 -0
  59. package/frontend/dist/assets/{railroadDiagram-RFXS5EU6-ac789252.js → railroadDiagram-RFXS5EU6-404ec27d.js} +1 -1
  60. package/frontend/dist/assets/{requirementDiagram-TGXJPOKE-0ce87e1d.js → requirementDiagram-TGXJPOKE-9b582bdd.js} +1 -1
  61. package/frontend/dist/assets/{sankeyDiagram-HTMAVEWB-4a0abb39.js → sankeyDiagram-HTMAVEWB-f6e0379c.js} +1 -1
  62. package/frontend/dist/assets/{sequenceDiagram-DBY2YBRQ-284dc47a.js → sequenceDiagram-DBY2YBRQ-508afe00.js} +1 -1
  63. package/frontend/dist/assets/{sizeCapture-X5ZJPWSS-253d1130.js → sizeCapture-X5ZJPWSS-ee602857.js} +1 -1
  64. package/frontend/dist/assets/{stateDiagram-2N3HPSRC-5baf911d.js → stateDiagram-2N3HPSRC-1e66e9f0.js} +1 -1
  65. package/frontend/dist/assets/stateDiagram-v2-6OUMAXLB-660e6df7.js +1 -0
  66. package/frontend/dist/assets/{swimlanes-5IMT3BWC-1587bceb.js → swimlanes-5IMT3BWC-5fa2813b.js} +1 -1
  67. package/frontend/dist/assets/swimlanesDiagram-G3AALYLV-f402ef76.js +8 -0
  68. package/frontend/dist/assets/{timeline-definition-FHXFAJF6-644a682a.js → timeline-definition-FHXFAJF6-12edf3b3.js} +1 -1
  69. package/frontend/dist/assets/treeView-QDETBFTQ-144f174a.js +1 -0
  70. package/frontend/dist/assets/treemap-6X3UGDF4-1c06331f.js +1 -0
  71. package/frontend/dist/assets/{vennDiagram-L72KCM5P-27901c69.js → vennDiagram-L72KCM5P-6f3acb6a.js} +1 -1
  72. package/frontend/dist/assets/wardley-OPB4EBWU-abf74bee.js +1 -0
  73. package/frontend/dist/assets/{wardleyDiagram-EHGQE667-7816c0a5.js → wardleyDiagram-EHGQE667-bf6c6922.js} +1 -1
  74. package/frontend/dist/assets/{xychartDiagram-FW5EYKEG-a3ce4246.js → xychartDiagram-FW5EYKEG-3f6dfba6.js} +1 -1
  75. package/frontend/dist/index.html +2 -2
  76. package/package.json +1 -1
  77. package/frontend/dist/assets/architecture-TIHT7OUA-a639eab7.js +0 -1
  78. package/frontend/dist/assets/channel-58a90d4a.js +0 -1
  79. package/frontend/dist/assets/classDiagram-OUVF2IWQ-2985892f.js +0 -1
  80. package/frontend/dist/assets/classDiagram-v2-EOCWNBFH-2985892f.js +0 -1
  81. package/frontend/dist/assets/cynefin-VYW2F7L2-68d8e42f.js +0 -1
  82. package/frontend/dist/assets/eventmodeling-45OFAUF4-ca978038.js +0 -1
  83. package/frontend/dist/assets/flowDiagram-23GEKE2U-e940d995.js +0 -1
  84. package/frontend/dist/assets/gitGraph-TEB2WS4Q-8176fbb9.js +0 -1
  85. package/frontend/dist/assets/index-647c7909.js +0 -559
  86. package/frontend/dist/assets/info-DKCQHKI2-fa612c43.js +0 -1
  87. package/frontend/dist/assets/packet-7NZHBO7P-069cb37a.js +0 -1
  88. package/frontend/dist/assets/pie-RZYD4A2V-58f1df35.js +0 -1
  89. package/frontend/dist/assets/radar-I7S5WNFK-72e8d492.js +0 -1
  90. package/frontend/dist/assets/railroad-3IZDKUUU-c75f2799.js +0 -1
  91. package/frontend/dist/assets/railroad-abnf-AHOZXSZD-c58af3ba.js +0 -1
  92. package/frontend/dist/assets/railroad-ebnf-EBAXGLYW-99be0db3.js +0 -1
  93. package/frontend/dist/assets/railroad-peg-LSFZ7HO6-e22021de.js +0 -1
  94. package/frontend/dist/assets/stateDiagram-v2-6OUMAXLB-f84f63b8.js +0 -1
  95. package/frontend/dist/assets/swimlanesDiagram-G3AALYLV-b6a155f6.js +0 -8
  96. package/frontend/dist/assets/treeView-QDETBFTQ-d9f2bb66.js +0 -1
  97. package/frontend/dist/assets/treemap-6X3UGDF4-3377c08d.js +0 -1
  98. package/frontend/dist/assets/wardley-OPB4EBWU-05517e1a.js +0 -1
@@ -37683,7 +37683,8 @@ function normalizeRelationship(rel) {
37683
37683
  }
37684
37684
  function fillAttributeDefaults(entity) {
37685
37685
  const fix = /* @__PURE__ */ __name((attrs) => {
37686
- for (const a of attrs ?? []) {
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);
@@ -45760,7 +45761,7 @@ var init_dictionaryService = __esm({
45760
45761
  let relationships = [];
45761
45762
  let cases = [];
45762
45763
  try {
45763
- const model = await loadPackage(packageName);
45764
+ const model = await loadPackage(String(dirPath));
45764
45765
  entities = model.entities;
45765
45766
  relationships = model.relationships;
45766
45767
  cases = model.cases.map((c) => ({
@@ -48645,6 +48646,51 @@ var init_serviceService = __esm({
48645
48646
  };
48646
48647
  }
48647
48648
  }
48649
+ /**
48650
+ * Move an entity from one package to another (#move-entity). The entity keeps
48651
+ * its UUID, so every UUID-based reference (relationships — cross-package is
48652
+ * first-class — cases, diagrams, perspectives) keeps resolving; nothing is
48653
+ * orphaned. We write the entity into the target package, then remove it from
48654
+ * the source via the projection directly — this bypasses deleteEntity's
48655
+ * same-package relationship guard, which is meant for destruction, not a
48656
+ * relocation where the references remain valid.
48657
+ */
48658
+ async moveEntity(sourcePackage, entityName, targetPackage) {
48659
+ logger.info(`Moving entity: ${sourcePackage}.${entityName} -> ${targetPackage}`);
48660
+ if (!targetPackage || targetPackage === sourcePackage) {
48661
+ return { success: false, errors: ["Target package must differ from the source package"] };
48662
+ }
48663
+ try {
48664
+ const entity = await readEntityFile(sourcePackage, entityName);
48665
+ if (!entity) {
48666
+ return { success: false, errors: [`Entity ${sourcePackage}.${entityName} not found`] };
48667
+ }
48668
+ const packages = await listMicroservices();
48669
+ if (!packages.includes(targetPackage)) {
48670
+ return { success: false, errors: [`Target package '${targetPackage}' does not exist`] };
48671
+ }
48672
+ const existingInTarget = await readEntityFile(targetPackage, entityName);
48673
+ if (existingInTarget) {
48674
+ return {
48675
+ success: false,
48676
+ errors: [`Target package '${targetPackage}' already has an entity named '${entityName}'`]
48677
+ };
48678
+ }
48679
+ const projection = getProjection(wsId("dictionaries"));
48680
+ entity.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
48681
+ await projection.writeEntity(`packages/${targetPackage}/entities/${entity.name}`, entity);
48682
+ const removed = await projection.deleteEntity(`packages/${sourcePackage}/entities/${entity.name}`);
48683
+ if (!removed) {
48684
+ await projection.deleteEntity(`packages/${targetPackage}/entities/${entity.name}`).catch(() => {
48685
+ });
48686
+ return { success: false, errors: ["Failed to remove the entity from the source package"] };
48687
+ }
48688
+ return { success: true, errors: [] };
48689
+ } catch (error48) {
48690
+ logger.error(`Error moving entity: ${error48}`);
48691
+ return { success: false, errors: [`Error moving entity: ${error48}`] };
48692
+ }
48693
+ }
48648
48694
  // --- Relationship CRUD ---
48649
48695
  async getPackageRelationships(packageName) {
48650
48696
  const packagePath = getPackagePath(packageName);
@@ -49107,424 +49153,6 @@ var init_serviceService = __esm({
49107
49153
  }
49108
49154
  });
49109
49155
 
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
49156
  // backend/node_modules/zod/v4/core/core.js
49529
49157
  // @__NO_SIDE_EFFECTS__
49530
49158
  function $constructor(name21, initializer3, params) {
@@ -50508,7 +50136,7 @@ var init_errors = __esm({
50508
50136
  });
50509
50137
 
50510
50138
  // backend/node_modules/zod/v4/core/parse.js
50511
- var _parse, parse4, _parseAsync, parseAsync, _safeParse, safeParse, _safeParseAsync, safeParseAsync, _encode, encode, _decode, decode, _encodeAsync, encodeAsync, _decodeAsync, decodeAsync, _safeEncode, safeEncode, _safeDecode, safeDecode, _safeEncodeAsync, safeEncodeAsync, _safeDecodeAsync, safeDecodeAsync;
50139
+ 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
50140
  var init_parse = __esm({
50513
50141
  "backend/node_modules/zod/v4/core/parse.js"() {
50514
50142
  init_core();
@@ -50527,7 +50155,7 @@ var init_parse = __esm({
50527
50155
  }
50528
50156
  return result.value;
50529
50157
  }, "_parse");
50530
- parse4 = /* @__PURE__ */ _parse($ZodRealError);
50158
+ parse3 = /* @__PURE__ */ _parse($ZodRealError);
50531
50159
  _parseAsync = /* @__PURE__ */ __name((_Err) => async (schema, value, _ctx, params) => {
50532
50160
  const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
50533
50161
  let result = schema._zod.run({ value, issues: [] }, ctx);
@@ -53285,10 +52913,10 @@ var init_schemas = __esm({
53285
52913
  throw new Error("implement() must be called with a function");
53286
52914
  }
53287
52915
  return function(...args) {
53288
- const parsedArgs = inst._def.input ? parse4(inst._def.input, args) : args;
52916
+ const parsedArgs = inst._def.input ? parse3(inst._def.input, args) : args;
53289
52917
  const result = Reflect.apply(func, this, parsedArgs);
53290
52918
  if (inst._def.output) {
53291
- return parse4(inst._def.output, result);
52919
+ return parse3(inst._def.output, result);
53292
52920
  }
53293
52921
  return result;
53294
52922
  };
@@ -61906,7 +61534,7 @@ __export(core_exports2, {
61906
61534
  isValidJWT: () => isValidJWT,
61907
61535
  locales: () => locales_exports,
61908
61536
  meta: () => meta,
61909
- parse: () => parse4,
61537
+ parse: () => parse3,
61910
61538
  parseAsync: () => parseAsync,
61911
61539
  prettifyError: () => prettifyError,
61912
61540
  process: () => process2,
@@ -62085,12 +61713,12 @@ var init_errors2 = __esm({
62085
61713
  });
62086
61714
 
62087
61715
  // backend/node_modules/zod/v4/classic/parse.js
62088
- var parse5, parseAsync2, safeParse2, safeParseAsync2, encode2, decode2, encodeAsync2, decodeAsync2, safeEncode2, safeDecode2, safeEncodeAsync2, safeDecodeAsync2;
61716
+ var parse4, parseAsync2, safeParse2, safeParseAsync2, encode2, decode2, encodeAsync2, decodeAsync2, safeEncode2, safeDecode2, safeEncodeAsync2, safeDecodeAsync2;
62089
61717
  var init_parse2 = __esm({
62090
61718
  "backend/node_modules/zod/v4/classic/parse.js"() {
62091
61719
  init_core2();
62092
61720
  init_errors2();
62093
- parse5 = /* @__PURE__ */ _parse(ZodRealError);
61721
+ parse4 = /* @__PURE__ */ _parse(ZodRealError);
62094
61722
  parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
62095
61723
  safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
62096
61724
  safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
@@ -62766,7 +62394,7 @@ var init_schemas2 = __esm({
62766
62394
  reg.add(inst, meta3);
62767
62395
  return inst;
62768
62396
  });
62769
- inst.parse = (data, params) => parse5(inst, data, params, { callee: inst.parse });
62397
+ inst.parse = (data, params) => parse4(inst, data, params, { callee: inst.parse });
62770
62398
  inst.safeParse = (data, params) => safeParse2(inst, data, params);
62771
62399
  inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
62772
62400
  inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
@@ -64193,7 +63821,7 @@ __export(external_exports, {
64193
63821
  object: () => object,
64194
63822
  optional: () => optional,
64195
63823
  overwrite: () => _overwrite,
64196
- parse: () => parse5,
63824
+ parse: () => parse4,
64197
63825
  parseAsync: () => parseAsync2,
64198
63826
  partialRecord: () => partialRecord,
64199
63827
  pipe: () => pipe,
@@ -64308,6 +63936,424 @@ var init_agentToolRegistry = __esm({
64308
63936
  }
64309
63937
  });
64310
63938
 
63939
+ // backend/src/models/Rule.ts
63940
+ function validateRule(rule) {
63941
+ const errors = [];
63942
+ if (!rule.name || typeof rule.name !== "string") {
63943
+ errors.push("Rule name is required");
63944
+ } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rule.name)) {
63945
+ errors.push("Rule name must be kebab-case (lowercase letters, numbers, hyphens)");
63946
+ }
63947
+ if (!rule.description || typeof rule.description !== "string") {
63948
+ errors.push("Rule description is required");
63949
+ }
63950
+ if (!rule.severity || !["info", "warning", "error"].includes(rule.severity)) {
63951
+ errors.push("Rule severity must be one of: info, warning, error");
63952
+ }
63953
+ if (!rule.enforcement || !["save", "process", "advisory"].includes(rule.enforcement)) {
63954
+ errors.push("Rule enforcement must be one of: save, process, advisory");
63955
+ }
63956
+ if (!rule.scope || !["entity", "package", "case", "global"].includes(rule.scope)) {
63957
+ errors.push("Rule scope must be one of: entity, package, case, global");
63958
+ }
63959
+ if (!Array.isArray(rule.targets) || rule.targets.length === 0) {
63960
+ errors.push("Rule must have at least one target");
63961
+ }
63962
+ if (rule.scope === "entity" && !rule.entityUuid) {
63963
+ errors.push("Entity-scoped rules must specify entityUuid");
63964
+ }
63965
+ if (rule.scope === "package" && !rule.packageName) {
63966
+ errors.push("Package-scoped rules must specify packageName");
63967
+ }
63968
+ if (rule.scope === "case" && !rule.caseUuid) {
63969
+ errors.push("Case-scoped rules must specify caseUuid");
63970
+ }
63971
+ if (rule.enforcement === "process") {
63972
+ const stageField = (rule.metadata || []).find((m) => m.name === "process-stage-field");
63973
+ if (!stageField || !stageField.value) {
63974
+ errors.push("Process-enforcement rules must reference a metadata field via process-stage-field");
63975
+ }
63976
+ }
63977
+ return errors;
63978
+ }
63979
+ var init_Rule = __esm({
63980
+ "backend/src/models/Rule.ts"() {
63981
+ "use strict";
63982
+ __name(validateRule, "validateRule");
63983
+ }
63984
+ });
63985
+
63986
+ // backend/src/services/ruleService.ts
63987
+ var ruleService_exports = {};
63988
+ __export(ruleService_exports, {
63989
+ ruleService: () => ruleService
63990
+ });
63991
+ var RuleService, ruleService;
63992
+ var init_ruleService = __esm({
63993
+ "backend/src/services/ruleService.ts"() {
63994
+ "use strict";
63995
+ init_Rule();
63996
+ init_fileOperations();
63997
+ init_uuid();
63998
+ init_logger();
63999
+ init_ProjectionRegistry();
64000
+ init_types();
64001
+ RuleService = class {
64002
+ static {
64003
+ __name(this, "RuleService");
64004
+ }
64005
+ /** List all rules from all storage scopes, optionally filtered. */
64006
+ async listRules(filters = {}) {
64007
+ const all = [];
64008
+ if (!filters.scope || filters.scope === "entity") {
64009
+ const files = await listAllEntityRuleFiles();
64010
+ for (const { service, entityUuid } of files) {
64011
+ const rules = await readEntityRules(service, entityUuid);
64012
+ all.push(...rules);
64013
+ }
64014
+ }
64015
+ if (!filters.scope || filters.scope === "package") {
64016
+ const packages = filters.packageName ? [filters.packageName] : await listPackagesWithRules();
64017
+ for (const pkg of packages) {
64018
+ const rules = await readPackageRules(pkg);
64019
+ all.push(...rules);
64020
+ }
64021
+ }
64022
+ if (!filters.scope || filters.scope === "global") {
64023
+ const globalRules = await readGlobalRules();
64024
+ all.push(...globalRules);
64025
+ }
64026
+ if (!filters.scope || filters.scope === "case") {
64027
+ if (filters.caseUuid) {
64028
+ const rules = await readCaseRules(filters.caseUuid);
64029
+ all.push(...rules);
64030
+ } else {
64031
+ const cases = await listCases();
64032
+ for (const c of cases) {
64033
+ const rules = c.rules || [];
64034
+ all.push(...rules);
64035
+ }
64036
+ }
64037
+ }
64038
+ for (const rule of all) {
64039
+ if (!rule.enforcement) rule.enforcement = "advisory";
64040
+ }
64041
+ return all.filter((rule) => {
64042
+ if (filters.severity && rule.severity !== filters.severity) return false;
64043
+ if (filters.enforcement && rule.enforcement !== filters.enforcement) return false;
64044
+ if (filters.targetUuid) {
64045
+ const hit = rule.targets.some(
64046
+ (t) => t.uuid === filters.targetUuid || t.entityUuid === filters.targetUuid
64047
+ );
64048
+ if (!hit) return false;
64049
+ }
64050
+ return true;
64051
+ });
64052
+ }
64053
+ /** Get a single rule by uuid (scans all scopes). */
64054
+ async getRule(uuid3) {
64055
+ const all = await this.listRules();
64056
+ return all.find((r) => r.uuid === uuid3) || null;
64057
+ }
64058
+ /** List rules whose targets include this entity (or any of its attributes). */
64059
+ async listRulesForEntity(entityUuid) {
64060
+ const all = await this.listRules();
64061
+ return all.filter(
64062
+ (rule) => rule.targets.some(
64063
+ (t) => t.uuid === entityUuid || t.entityUuid === entityUuid
64064
+ )
64065
+ );
64066
+ }
64067
+ /** Create a rule. Storage location is determined by `scope`. */
64068
+ async createRule(input) {
64069
+ if (!input.enforcement) input.enforcement = "advisory";
64070
+ const errors = validateRule(input);
64071
+ if (errors.length > 0) {
64072
+ return { success: false, errors };
64073
+ }
64074
+ let rule = {
64075
+ uuid: generateUUID(),
64076
+ name: input.name,
64077
+ description: input.description,
64078
+ severity: input.severity,
64079
+ enforcement: input.enforcement,
64080
+ scope: input.scope,
64081
+ targets: input.targets,
64082
+ packageName: input.packageName,
64083
+ entityUuid: input.entityUuid,
64084
+ caseUuid: input.caseUuid,
64085
+ expression: input.expression,
64086
+ tags: input.tags,
64087
+ metadata: input.metadata,
64088
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
64089
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
64090
+ };
64091
+ rule = await this.applyAutoScope(rule);
64092
+ const saved = await this.saveRuleToScope(rule);
64093
+ if (!saved) return { success: false, errors: ["Failed to write rule to storage"] };
64094
+ return { success: true, rule };
64095
+ }
64096
+ /** Update an existing rule by uuid. Re-routes to new scope if scope changed. */
64097
+ async updateRule(uuid3, input) {
64098
+ const existing = await this.getRule(uuid3);
64099
+ if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
64100
+ let merged = {
64101
+ ...existing,
64102
+ ...input,
64103
+ uuid: existing.uuid,
64104
+ createdAt: existing.createdAt,
64105
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
64106
+ };
64107
+ const errors = validateRule(merged);
64108
+ if (errors.length > 0) return { success: false, errors };
64109
+ merged = await this.applyAutoScope(merged, existing.scope, existing.packageName);
64110
+ const scopeMoved = existing.scope !== merged.scope || existing.packageName !== merged.packageName || existing.entityUuid !== merged.entityUuid || existing.caseUuid !== merged.caseUuid;
64111
+ if (scopeMoved) {
64112
+ await this.removeRuleFromScope(existing);
64113
+ }
64114
+ const saved = await this.saveRuleToScope(merged);
64115
+ if (!saved) return { success: false, errors: ["Failed to write updated rule to storage"] };
64116
+ return { success: true, rule: merged };
64117
+ }
64118
+ /** Delete a rule by uuid. */
64119
+ async deleteRule(uuid3) {
64120
+ const existing = await this.getRule(uuid3);
64121
+ if (!existing) return { success: false, errors: [`Rule ${uuid3} not found`] };
64122
+ const removed = await this.removeRuleFromScope(existing);
64123
+ if (!removed) return { success: false, errors: ["Failed to remove rule from storage"] };
64124
+ return { success: true };
64125
+ }
64126
+ // ─── Private helpers: route to the right storage location ────────────────
64127
+ async saveRuleToScope(rule) {
64128
+ const projection = getProjection(wsId("dictionaries"));
64129
+ switch (rule.scope) {
64130
+ case "entity": {
64131
+ if (!rule.entityUuid) return false;
64132
+ const files = await listAllEntityRuleFiles();
64133
+ let service = files.find((f) => f.entityUuid === rule.entityUuid)?.service;
64134
+ if (!service) service = rule.packageName;
64135
+ if (!service) {
64136
+ logger.error(`Cannot determine service for entity ${rule.entityUuid}`);
64137
+ return false;
64138
+ }
64139
+ const entity = await this.findEntityByUuid(service, rule.entityUuid);
64140
+ if (!entity) {
64141
+ logger.error(`Entity ${rule.entityUuid} not found in service ${service}`);
64142
+ return false;
64143
+ }
64144
+ const existing = entity.rules || [];
64145
+ const updatedRules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
64146
+ entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
64147
+ const logicalPath = `packages/${service}/entities/${entity.name}`;
64148
+ try {
64149
+ await projection.writeEntity(logicalPath, entity);
64150
+ return true;
64151
+ } catch (e) {
64152
+ logger.error(`Failed to write entity-scope rule via projection: ${e.message}`);
64153
+ return false;
64154
+ }
64155
+ }
64156
+ case "package": {
64157
+ if (!rule.packageName) return false;
64158
+ const existing = await readPackageRules(rule.packageName);
64159
+ const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
64160
+ const packageLogicalPath = `packages/${rule.packageName}`;
64161
+ try {
64162
+ await projection.writePackageRules(packageLogicalPath, updated);
64163
+ return true;
64164
+ } catch (e) {
64165
+ logger.error(`Failed to write package-scope rules via projection: ${e.message}`);
64166
+ return false;
64167
+ }
64168
+ }
64169
+ case "case": {
64170
+ if (!rule.caseUuid) return false;
64171
+ const c = await readCaseFile(rule.caseUuid);
64172
+ if (!c) return false;
64173
+ const existing = c.rules || [];
64174
+ c.rules = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
64175
+ c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
64176
+ const targetPackage = await this.resolveCaseHomePackage(c);
64177
+ if (!targetPackage) return false;
64178
+ const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
64179
+ try {
64180
+ await projection.writeCase(caseLogicalPath, c);
64181
+ return true;
64182
+ } catch (e) {
64183
+ logger.error(`Failed to write case-scope rule via projection: ${e.message}`);
64184
+ return false;
64185
+ }
64186
+ }
64187
+ case "global": {
64188
+ const existing = await readGlobalRules();
64189
+ const updated = [...existing.filter((r) => r.uuid !== rule.uuid), rule];
64190
+ try {
64191
+ await projection.writeGlobalRules(updated);
64192
+ return true;
64193
+ } catch (e) {
64194
+ logger.error(`Failed to write global rules via projection: ${e.message}`);
64195
+ return false;
64196
+ }
64197
+ }
64198
+ default:
64199
+ return false;
64200
+ }
64201
+ }
64202
+ async removeRuleFromScope(rule) {
64203
+ const projection = getProjection(wsId("dictionaries"));
64204
+ switch (rule.scope) {
64205
+ case "entity": {
64206
+ if (!rule.entityUuid) return false;
64207
+ const files = await listAllEntityRuleFiles();
64208
+ const service = files.find((f) => f.entityUuid === rule.entityUuid)?.service ?? rule.packageName;
64209
+ if (!service) return false;
64210
+ const entity = await this.findEntityByUuid(service, rule.entityUuid);
64211
+ if (!entity) return false;
64212
+ const existing = entity.rules || [];
64213
+ const updatedRules = existing.filter((r) => r.uuid !== rule.uuid);
64214
+ entity.rules = updatedRules.length > 0 ? updatedRules : void 0;
64215
+ const logicalPath = `packages/${service}/entities/${entity.name}`;
64216
+ try {
64217
+ await projection.writeEntity(logicalPath, entity);
64218
+ return true;
64219
+ } catch (e) {
64220
+ logger.error(`Failed to remove entity-scope rule via projection: ${e.message}`);
64221
+ return false;
64222
+ }
64223
+ }
64224
+ case "package": {
64225
+ if (!rule.packageName) return false;
64226
+ const existing = await readPackageRules(rule.packageName);
64227
+ const updated = existing.filter((r) => r.uuid !== rule.uuid);
64228
+ const packageLogicalPath = `packages/${rule.packageName}`;
64229
+ try {
64230
+ await projection.writePackageRules(packageLogicalPath, updated);
64231
+ return true;
64232
+ } catch (e) {
64233
+ logger.error(`Failed to remove package-scope rule via projection: ${e.message}`);
64234
+ return false;
64235
+ }
64236
+ }
64237
+ case "case": {
64238
+ if (!rule.caseUuid) return false;
64239
+ const c = await readCaseFile(rule.caseUuid);
64240
+ if (!c) return false;
64241
+ const existing = c.rules || [];
64242
+ const updated = existing.filter((r) => r.uuid !== rule.uuid);
64243
+ c.rules = updated.length > 0 ? updated : void 0;
64244
+ c.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
64245
+ const targetPackage = await this.resolveCaseHomePackage(c);
64246
+ if (!targetPackage) return false;
64247
+ const caseLogicalPath = `packages/${targetPackage}/cases/${c.name}`;
64248
+ try {
64249
+ await projection.writeCase(caseLogicalPath, c);
64250
+ return true;
64251
+ } catch (e) {
64252
+ logger.error(`Failed to remove case-scope rule via projection: ${e.message}`);
64253
+ return false;
64254
+ }
64255
+ }
64256
+ case "global": {
64257
+ const existing = await readGlobalRules();
64258
+ const updated = existing.filter((r) => r.uuid !== rule.uuid);
64259
+ try {
64260
+ await projection.writeGlobalRules(updated);
64261
+ return true;
64262
+ } catch (e) {
64263
+ logger.error(`Failed to remove global rule via projection: ${e.message}`);
64264
+ return false;
64265
+ }
64266
+ }
64267
+ default:
64268
+ return false;
64269
+ }
64270
+ }
64271
+ /**
64272
+ * Load an entity from a package by uuid. Returns null if not found.
64273
+ * Used by entity-scope rule writes to round-trip the parent entity
64274
+ * through `projection.writeEntity`.
64275
+ */
64276
+ async findEntityByUuid(service, entityUuid) {
64277
+ try {
64278
+ const pkg = await loadPackage(service);
64279
+ return pkg.entities.find((e) => e.uuid === entityUuid) ?? null;
64280
+ } catch (error48) {
64281
+ logger.error(`Error loading package ${service} for entity lookup: ${error48}`);
64282
+ return null;
64283
+ }
64284
+ }
64285
+ /**
64286
+ * Resolve the home package for a case so we can construct a
64287
+ * `packages/<pkg>/cases/<name>` logical path. Mirrors the heuristic in
64288
+ * caseService.ts / fileOperations.ts: existing-owner lookup by uuid first,
64289
+ * then first root entity's package, then first package on disk.
64290
+ */
64291
+ async resolveCaseHomePackage(c) {
64292
+ const { listPackages: listPackages3 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
64293
+ const packages = await listPackages3();
64294
+ for (const pkg of packages) {
64295
+ try {
64296
+ const model = await loadPackage(pkg);
64297
+ if (model.ownership.caseByUuid.has(c.uuid)) return pkg;
64298
+ } catch {
64299
+ }
64300
+ }
64301
+ if (c.rootEntities && c.rootEntities.length > 0) {
64302
+ const rootUuid = c.rootEntities[0];
64303
+ for (const pkg of packages) {
64304
+ try {
64305
+ const model = await loadPackage(pkg);
64306
+ if (model.ownership.entityByUuid.has(rootUuid)) return pkg;
64307
+ } catch {
64308
+ }
64309
+ }
64310
+ }
64311
+ return packages[0] || null;
64312
+ }
64313
+ // ─── Auto-promote / auto-demote (#75) ────────────────────────────────
64314
+ /**
64315
+ * Determine the correct scope for a rule based on its targets.
64316
+ * Called before save. If targets span 2+ packages → global.
64317
+ * If all targets are in one package → that package's scope (keep
64318
+ * the caller's intent for entity vs package).
64319
+ */
64320
+ resolveScope(rule) {
64321
+ const packages = /* @__PURE__ */ new Set();
64322
+ for (const t of rule.targets) {
64323
+ if (t.packageName) packages.add(t.packageName);
64324
+ }
64325
+ if (packages.size >= 2) {
64326
+ return { scope: "global" };
64327
+ }
64328
+ if (packages.size === 1) {
64329
+ const pkg = [...packages][0];
64330
+ if (rule.scope === "global") {
64331
+ return { scope: "package", packageName: pkg };
64332
+ }
64333
+ }
64334
+ return { scope: rule.scope, packageName: rule.packageName };
64335
+ }
64336
+ /**
64337
+ * Wrap around createRule/updateRule to apply auto-promote/demote.
64338
+ * Checks if the resolved scope differs from what the caller requested
64339
+ * and adjusts accordingly — including moving the rule between files.
64340
+ */
64341
+ async applyAutoScope(rule, _oldScope, _oldPkg) {
64342
+ const resolved = this.resolveScope(rule);
64343
+ if (resolved.scope !== rule.scope) {
64344
+ logger.info(`Auto-${resolved.scope === "global" ? "promote" : "demote"} rule ${rule.uuid}: ${rule.scope} \u2192 ${resolved.scope}`);
64345
+ }
64346
+ rule.scope = resolved.scope;
64347
+ if (resolved.packageName !== void 0) {
64348
+ rule.packageName = resolved.packageName;
64349
+ }
64350
+ return rule;
64351
+ }
64352
+ };
64353
+ ruleService = new RuleService();
64354
+ }
64355
+ });
64356
+
64311
64357
  // backend/src/services/versionService.ts
64312
64358
  var versionService_exports = {};
64313
64359
  __export(versionService_exports, {
@@ -116930,8 +116976,8 @@ function resolveToolCategory(toolName, trustByName) {
116930
116976
  const clean = toolName.replace(/^functions\./, "").split(":")[0];
116931
116977
  const builtin = TOOL_CATEGORY_MAP[clean];
116932
116978
  if (builtin) return builtin;
116933
- const registered4 = getAgentTool(clean)?.category;
116934
- if (registered4) return registered4;
116979
+ const registered5 = getAgentTool(clean)?.category;
116980
+ if (registered5) return registered5;
116935
116981
  if (clean.includes(".")) {
116936
116982
  const trust = trustByName.get(clean);
116937
116983
  return trust === "auto" ? "read" : "modify";
@@ -159537,6 +159583,24 @@ var setEntityHidden = /* @__PURE__ */ __name(async (req, res) => {
159537
159583
  res.status(500).json({ message: "Error setting entity visibility", error: error48 });
159538
159584
  }
159539
159585
  }, "setEntityHidden");
159586
+ var moveEntity = /* @__PURE__ */ __name(async (req, res) => {
159587
+ try {
159588
+ const { service, entity } = req.params;
159589
+ const targetPackage = typeof req.body?.targetPackage === "string" ? req.body.targetPackage : "";
159590
+ if (!targetPackage) {
159591
+ return res.status(400).json({ message: "targetPackage is required" });
159592
+ }
159593
+ const result = await serviceService.moveEntity(service, entity, targetPackage);
159594
+ if (!result.success) {
159595
+ const notFound = result.errors.some((e) => e.includes("not found"));
159596
+ return res.status(notFound ? 404 : 400).json({ message: "Failed to move entity", errors: result.errors });
159597
+ }
159598
+ res.json({ message: "Entity moved", data: { service, entity, targetPackage } });
159599
+ } catch (error48) {
159600
+ logger.error(`Error moving entity: ${error48}`);
159601
+ res.status(500).json({ message: "Error moving entity", error: error48 });
159602
+ }
159603
+ }, "moveEntity");
159540
159604
  var setEntityStyle = /* @__PURE__ */ __name(async (req, res) => {
159541
159605
  try {
159542
159606
  const { service, entity } = req.params;
@@ -159763,7 +159827,46 @@ var deleteRelationship = /* @__PURE__ */ __name(async (req, res) => {
159763
159827
  }
159764
159828
  }, "deleteRelationship");
159765
159829
 
159830
+ // backend/src/services/entityMove/agentTools.ts
159831
+ init_zod();
159832
+ init_agentToolRegistry();
159833
+ init_serviceService();
159834
+ var registered = false;
159835
+ function registerMoveEntityAgentTool() {
159836
+ if (registered) return;
159837
+ registered = true;
159838
+ registerAgentTool({
159839
+ name: "moveEntity",
159840
+ category: "modify",
159841
+ 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.",
159842
+ jsonSchema: {
159843
+ type: "object",
159844
+ required: ["sourcePackage", "entityName", "targetPackage"],
159845
+ properties: {
159846
+ sourcePackage: { type: "string", description: "Package the entity currently lives in" },
159847
+ entityName: { type: "string", description: "Name of the entity to move" },
159848
+ targetPackage: { type: "string", description: "Package to move the entity into" }
159849
+ }
159850
+ },
159851
+ inputSchema: external_exports.object({
159852
+ sourcePackage: external_exports.string(),
159853
+ entityName: external_exports.string(),
159854
+ targetPackage: external_exports.string()
159855
+ }),
159856
+ execute: /* @__PURE__ */ __name(async (args) => {
159857
+ const r = await serviceService.moveEntity(
159858
+ String(args.sourcePackage),
159859
+ String(args.entityName),
159860
+ String(args.targetPackage)
159861
+ );
159862
+ return r.success ? { success: true, entity: args.entityName, from: args.sourcePackage, to: args.targetPackage } : { success: false, errors: r.errors };
159863
+ }, "execute")
159864
+ });
159865
+ }
159866
+ __name(registerMoveEntityAgentTool, "registerMoveEntityAgentTool");
159867
+
159766
159868
  // backend/src/routes/data-dictionary/entity.routes.ts
159869
+ registerMoveEntityAgentTool();
159767
159870
  var router3 = (0, import_express4.Router)();
159768
159871
  router3.get("/api/entities/:microservice/:entityName/attributes", getEntityAttributes);
159769
159872
  router3.get("/api/entities/:microservice/:entityName/related", getRelatedEntities);
@@ -159777,6 +159880,7 @@ router3.put("/api/services/:service/entities/:entity", authorizeJwt(["admin" /*
159777
159880
  router3.delete("/api/services/:service/entities/:entity", authorizeJwt(["admin" /* ADMIN */]), deleteEntity);
159778
159881
  router3.put("/api/services/:service/entities/:entity/hidden", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), setEntityHidden);
159779
159882
  router3.put("/api/services/:service/entities/:entity/style", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), setEntityStyle);
159883
+ router3.put("/api/services/:service/entities/:entity/move", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), moveEntity);
159780
159884
  router3.post("/api/services/:service/entities/:entity/submit", authorizeJwt(["admin" /* ADMIN */, "editor" /* EDITOR */]), submitEntity);
159781
159885
  router3.post("/api/services/:service/entities/:entity/approve", authorizeJwt(["admin" /* ADMIN */]), approveEntity);
159782
159886
  router3.post("/api/services/:service/entities/:entity/return", authorizeJwt(["admin" /* ADMIN */]), returnEntity);
@@ -160321,10 +160425,10 @@ init_zod();
160321
160425
  init_agentToolRegistry();
160322
160426
  init_dicoConfigService();
160323
160427
  init_serviceService();
160324
- var registered = false;
160428
+ var registered2 = false;
160325
160429
  function registerElementStyleAgentTools() {
160326
- if (registered) return;
160327
- registered = true;
160430
+ if (registered2) return;
160431
+ registered2 = true;
160328
160432
  registerAgentTool({
160329
160433
  name: "defineElementStyle",
160330
160434
  category: "modify",
@@ -165089,10 +165193,10 @@ init_zod();
165089
165193
  init_agentToolRegistry();
165090
165194
  init_searchIndexService();
165091
165195
  var KINDS = ["entity", "attribute", "package", "relationship", "rule", "metadata", "case"];
165092
- var registered2 = false;
165196
+ var registered3 = false;
165093
165197
  function registerSearchAgentTools() {
165094
- if (registered2) return;
165095
- registered2 = true;
165198
+ if (registered3) return;
165199
+ registered3 = true;
165096
165200
  registerAgentTool({
165097
165201
  name: "searchModel",
165098
165202
  category: "read",
@@ -173535,10 +173639,10 @@ function getSynthesisBrief(dataDir, entityName) {
173535
173639
  __name(getSynthesisBrief, "getSynthesisBrief");
173536
173640
 
173537
173641
  // backend/src/services/reverseEngineer/agentTools.ts
173538
- var registered3 = false;
173642
+ var registered4 = false;
173539
173643
  function registerReverseEngineerAgentTools() {
173540
- if (registered3) return;
173541
- registered3 = true;
173644
+ if (registered4) return;
173645
+ registered4 = true;
173542
173646
  registerAgentTool({
173543
173647
  name: "listSynthesisBriefs",
173544
173648
  category: "read",