VerifyLobsterExecutionPolicy.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. import java.sql.*;
  2. import java.util.*;
  3. /**
  4. * Verify effective lobster execution policy for a tenant + company.
  5. * Mirrors LobsterExecutionPolicyService.resolve merge logic.
  6. */
  7. public class VerifyLobsterExecutionPolicy {
  8. private static final String HOST = "cq-cdb-8fjmemkb.sql.tencentcdb.com";
  9. private static final int PORT = 27220;
  10. private static final String MASTER_USER = "root";
  11. private static final String MASTER_PWD = "Ylrz_1q2w3e4r5t6y";
  12. private static final String MASTER = "ylrz_saas";
  13. private static final long TENANT_ID = 33L;
  14. private static final long COMPANY_ID = 338L;
  15. public static void main(String[] args) throws Exception {
  16. long tenantId = args.length > 0 ? Long.parseLong(args[0]) : TENANT_ID;
  17. long companyId = args.length > 1 ? Long.parseLong(args[1]) : COMPANY_ID;
  18. TenantDb tenant = resolveTenantDb(tenantId);
  19. System.out.println("=== Tenant " + tenantId + " db=" + tenant.database + " companyId=" + companyId + " ===\n");
  20. try (Connection c = connect(tenant.database, tenant.user, tenant.password)) {
  21. printGlobalConfig(c);
  22. printTaskCopies(c, companyId);
  23. printTagBindings(c, companyId);
  24. printEffectivePolicies(c, companyId);
  25. printSupplementary(c, companyId);
  26. }
  27. }
  28. private static void printSupplementary(Connection c, long companyId) throws SQLException {
  29. System.out.println("--- Supplementary stats ---");
  30. runQuery(c, "tag rel by template_id",
  31. "SELECT template_id, COUNT(*) cnt FROM company_lobster_tag_user_rel "
  32. + "WHERE company_id=? AND del_flag=0 GROUP BY template_id ORDER BY cnt DESC LIMIT 10", companyId);
  33. runQuery(c, "instance by status",
  34. "SELECT status, COUNT(*) cnt FROM lobster_workflow_instance "
  35. + "WHERE company_id=? GROUP BY status", companyId);
  36. runQuery(c, "tag-template bindings",
  37. "SELECT id, tag_code, task_template_id, status FROM company_tag_template_binding "
  38. + "WHERE company_id=? AND del_flag=0 ORDER BY id DESC LIMIT 8", companyId);
  39. }
  40. private static void runQuery(Connection c, String label, String sql, long companyId) throws SQLException {
  41. System.out.println("[" + label + "]");
  42. if (!tableExists(c, sql.contains("company_lobster_tag_user_rel") ? "company_lobster_tag_user_rel"
  43. : sql.contains("lobster_workflow_instance") ? "lobster_workflow_instance"
  44. : "company_tag_template_binding")) {
  45. System.out.println(" (table missing)");
  46. return;
  47. }
  48. try (PreparedStatement ps = c.prepareStatement(sql)) {
  49. ps.setLong(1, companyId);
  50. try (ResultSet rs = ps.executeQuery()) {
  51. int n = 0;
  52. while (rs.next()) {
  53. n++;
  54. printRow(rs);
  55. }
  56. if (n == 0) System.out.println(" (empty)");
  57. }
  58. }
  59. System.out.println();
  60. }
  61. private static void printGlobalConfig(Connection c) throws SQLException {
  62. System.out.println("--- lobster_execution_config (tenant-global, company_id=0) ---");
  63. if (!tableExists(c, "lobster_execution_config")) {
  64. System.out.println("(table missing -> code defaults: HYBRID, autoStart=true)\n");
  65. return;
  66. }
  67. String sql = "SELECT id, company_id, default_execution_mode, auto_start_instance_on_tag, "
  68. + "quality_gate_enabled, quality_pass_score_percent, message_dedup_enabled, update_time "
  69. + "FROM lobster_execution_config WHERE company_id=0 LIMIT 1";
  70. try (Statement st = c.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  71. if (!rs.next()) {
  72. System.out.println("(no row -> code defaults: HYBRID, autoStart=true)\n");
  73. return;
  74. }
  75. printRow(rs);
  76. }
  77. System.out.println();
  78. }
  79. private static void printTaskCopies(Connection c, long companyId) throws SQLException {
  80. System.out.println("--- company_workflow_lobster task_copy (company_id=" + companyId + ") ---");
  81. if (!tableExists(c, "company_workflow_lobster")) {
  82. System.out.println("(table missing)\n");
  83. return;
  84. }
  85. String sql = "SELECT id, template_name, template_kind, status, execution_mode, "
  86. + "enable_content_personalization, enable_flow_personalization, "
  87. + "strict_fixed_workflow, auto_start_instance_on_tag, source_template_id, update_time "
  88. + "FROM company_workflow_lobster "
  89. + "WHERE company_id=? AND del_flag=0 AND template_kind='task_copy' "
  90. + "ORDER BY update_time DESC LIMIT 20";
  91. try (PreparedStatement ps = c.prepareStatement(sql)) {
  92. ps.setLong(1, companyId);
  93. try (ResultSet rs = ps.executeQuery()) {
  94. int n = 0;
  95. while (rs.next()) {
  96. n++;
  97. System.out.println("#" + n + " id=" + rs.getLong("id")
  98. + " name=" + rs.getString("template_name")
  99. + " status=" + rs.getInt("status"));
  100. System.out.println(" execution_mode=" + nv(rs.getString("execution_mode"))
  101. + " contentPers=" + nv(rs.getObject("enable_content_personalization"))
  102. + " flowPers=" + nv(rs.getObject("enable_flow_personalization"))
  103. + " strict=" + nv(rs.getObject("strict_fixed_workflow"))
  104. + " autoStart=" + nv(rs.getObject("auto_start_instance_on_tag")));
  105. System.out.println(" source_template_id=" + nv(rs.getObject("source_template_id"))
  106. + " updated=" + rs.getTimestamp("update_time"));
  107. }
  108. if (n == 0) System.out.println("(no task_copy rows)");
  109. }
  110. }
  111. System.out.println();
  112. }
  113. private static void printTagBindings(Connection c, long companyId) throws SQLException {
  114. System.out.println("--- company_lobster_tag_user_rel (sample bindings, company_id=" + companyId + ") ---");
  115. if (!tableExists(c, "company_lobster_tag_user_rel")) {
  116. System.out.println("(table missing)\n");
  117. return;
  118. }
  119. String sql = "SELECT rel.id, rel.template_id, rel.binding_id, rel.tag_code, rel.channel_type, "
  120. + "rel.channel_contact_id, wf.template_name, wf.execution_mode, wf.strict_fixed_workflow "
  121. + "FROM company_lobster_tag_user_rel rel "
  122. + "LEFT JOIN company_workflow_lobster wf ON wf.id=rel.template_id AND wf.company_id=rel.company_id "
  123. + "WHERE rel.company_id=? AND rel.del_flag=0 "
  124. + "ORDER BY rel.id DESC LIMIT 10";
  125. try (PreparedStatement ps = c.prepareStatement(sql)) {
  126. ps.setLong(1, companyId);
  127. try (ResultSet rs = ps.executeQuery()) {
  128. int n = 0;
  129. while (rs.next()) {
  130. n++;
  131. System.out.println("rel#" + rs.getLong("id")
  132. + " template_id=" + rs.getLong("template_id")
  133. + " binding_id=" + nv(rs.getObject("binding_id"))
  134. + " tag=" + rs.getString("tag_code")
  135. + " contact=" + rs.getLong("channel_contact_id"));
  136. System.out.println(" wf=" + nv(rs.getString("template_name"))
  137. + " wf.execution_mode=" + nv(rs.getString("execution_mode"))
  138. + " strict=" + nv(rs.getObject("strict_fixed_workflow")));
  139. }
  140. if (n == 0) System.out.println("(no tag-user rel rows)");
  141. }
  142. }
  143. System.out.println();
  144. }
  145. private static void printEffectivePolicies(Connection c, long companyId) throws SQLException {
  146. System.out.println("--- Effective policy (LobsterExecutionPolicyService.resolve logic) ---");
  147. Global g = loadGlobal(c);
  148. List<TaskCopy> copies = loadTaskCopies(c, companyId);
  149. System.out.println("Global baseline:");
  150. System.out.println(" executionMode=" + g.mode + " autoStartInstanceOnTag=" + g.autoStart
  151. + " qualityGate=" + g.qualityGate);
  152. System.out.println("\nPer task_copy (templateId -> merged effective):");
  153. if (copies.isEmpty()) {
  154. Policy p = merge(g, null);
  155. printPolicy("(no template / prompt-only contacts)", p);
  156. } else {
  157. for (TaskCopy tc : copies) {
  158. Policy p = merge(g, tc);
  159. printPolicy("templateId=" + tc.id + " \"" + tc.name + "\"", p);
  160. }
  161. }
  162. System.out.println("\nOrchestrator routing summary:");
  163. if (copies.isEmpty()) {
  164. Policy p = merge(g, null);
  165. System.out.println(" [unbound] preferInstance=" + p.preferInstance()
  166. + " allowPromptFallback=" + p.allowPromptFallback()
  167. + " allowFlow=" + p.allowFlow
  168. + " allowContent=" + p.allowContent
  169. + " -> " + describeRoute(p));
  170. } else {
  171. for (TaskCopy tc : copies) {
  172. Policy p = merge(g, tc);
  173. System.out.println(" [templateId=" + tc.id + "] preferInstance=" + p.preferInstance()
  174. + " allowPromptFallback=" + p.allowPromptFallback()
  175. + " allowFlow=" + p.allowFlow
  176. + " allowContent=" + p.allowContent
  177. + " -> " + describeRoute(p));
  178. }
  179. Policy unbound = merge(g, null);
  180. System.out.println(" [unbound/no tag rel] preferInstance=" + unbound.preferInstance()
  181. + " allowPromptFallback=" + unbound.allowPromptFallback()
  182. + " -> " + describeRoute(unbound));
  183. }
  184. }
  185. private static String describeRoute(Policy p) {
  186. if (p.strict) {
  187. return "INSTANCE-only (strict fixed, no prompt fallback, no flow personalization)";
  188. }
  189. switch (p.mode) {
  190. case "HYBRID":
  191. return "try instance first (autoStart=" + p.autoStart + "), else PROMPT chat fallback";
  192. case "INSTANCE":
  193. return "INSTANCE only; prompt fallback only if instance path fails";
  194. case "PROMPT":
  195. return "PROMPT chat only, no instance path";
  196. default:
  197. return p.mode;
  198. }
  199. }
  200. private static void printPolicy(String label, Policy p) {
  201. System.out.println(" " + label);
  202. System.out.println(" mode=" + p.mode + " strict=" + p.strict
  203. + " autoStart=" + p.autoStart
  204. + " contentPers=" + p.allowContent + " flowPers=" + p.allowFlow);
  205. }
  206. private static Global loadGlobal(Connection c) throws SQLException {
  207. Global g = new Global();
  208. g.mode = "HYBRID";
  209. g.autoStart = true;
  210. g.qualityGate = true;
  211. if (!tableExists(c, "lobster_execution_config")) return g;
  212. String sql = "SELECT default_execution_mode, auto_start_instance_on_tag, quality_gate_enabled "
  213. + "FROM lobster_execution_config WHERE company_id=0 LIMIT 1";
  214. try (Statement st = c.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  215. if (rs.next()) {
  216. if (rs.getString("default_execution_mode") != null && !rs.getString("default_execution_mode").isBlank()) {
  217. g.mode = rs.getString("default_execution_mode").trim().toUpperCase();
  218. }
  219. g.autoStart = intToBool(rs.getObject("auto_start_instance_on_tag"), true);
  220. g.qualityGate = intToBool(rs.getObject("quality_gate_enabled"), true);
  221. }
  222. }
  223. return g;
  224. }
  225. private static List<TaskCopy> loadTaskCopies(Connection c, long companyId) throws SQLException {
  226. List<TaskCopy> list = new ArrayList<>();
  227. if (!tableExists(c, "company_workflow_lobster")) return list;
  228. String sql = "SELECT id, template_name, execution_mode, enable_content_personalization, "
  229. + "enable_flow_personalization, strict_fixed_workflow, auto_start_instance_on_tag "
  230. + "FROM company_workflow_lobster "
  231. + "WHERE company_id=? AND del_flag=0 AND template_kind='task_copy' AND status=1 "
  232. + "ORDER BY update_time DESC";
  233. try (PreparedStatement ps = c.prepareStatement(sql)) {
  234. ps.setLong(1, companyId);
  235. try (ResultSet rs = ps.executeQuery()) {
  236. while (rs.next()) {
  237. TaskCopy t = new TaskCopy();
  238. t.id = rs.getLong("id");
  239. t.name = rs.getString("template_name");
  240. t.executionMode = rs.getString("execution_mode");
  241. t.contentPers = (Integer) rs.getObject("enable_content_personalization");
  242. t.flowPers = (Integer) rs.getObject("enable_flow_personalization");
  243. t.strict = (Integer) rs.getObject("strict_fixed_workflow");
  244. t.autoStart = (Integer) rs.getObject("auto_start_instance_on_tag");
  245. list.add(t);
  246. }
  247. }
  248. }
  249. return list;
  250. }
  251. private static Policy merge(Global g, TaskCopy tc) {
  252. Policy p = new Policy();
  253. p.mode = g.mode;
  254. p.autoStart = g.autoStart;
  255. p.allowContent = true;
  256. p.allowFlow = true;
  257. p.strict = false;
  258. if (tc != null) {
  259. if (tc.executionMode != null && !tc.executionMode.isBlank()) {
  260. p.mode = tc.executionMode.trim().toUpperCase();
  261. }
  262. if (tc.contentPers != null) p.allowContent = intToBool(tc.contentPers, true);
  263. if (tc.flowPers != null) p.allowFlow = intToBool(tc.flowPers, true);
  264. if (tc.strict != null) p.strict = intToBool(tc.strict, false);
  265. if (tc.autoStart != null) p.autoStart = intToBool(tc.autoStart, false);
  266. }
  267. if (p.strict) {
  268. p.allowFlow = false;
  269. if ("PROMPT".equals(p.mode)) p.mode = "INSTANCE";
  270. }
  271. return p;
  272. }
  273. private static boolean intToBool(Object v, boolean def) {
  274. if (v == null) return def;
  275. if (v instanceof Number) return ((Number) v).intValue() != 0;
  276. return Boolean.parseBoolean(v.toString());
  277. }
  278. private static String nv(Object o) {
  279. return o == null ? "null(inherit)" : String.valueOf(o);
  280. }
  281. private static void printRow(ResultSet rs) throws SQLException {
  282. ResultSetMetaData md = rs.getMetaData();
  283. for (int i = 1; i <= md.getColumnCount(); i++) {
  284. if (i > 1) System.out.print(" | ");
  285. System.out.print(md.getColumnLabel(i) + "=" + rs.getObject(i));
  286. }
  287. System.out.println();
  288. }
  289. private static boolean tableExists(Connection c, String table) throws SQLException {
  290. try (PreparedStatement ps = c.prepareStatement(
  291. "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?")) {
  292. ps.setString(1, table);
  293. try (ResultSet rs = ps.executeQuery()) {
  294. return rs.next() && rs.getInt(1) > 0;
  295. }
  296. }
  297. }
  298. private static TenantDb resolveTenantDb(long tenantId) throws SQLException {
  299. try (Connection master = connect(MASTER, MASTER_USER, MASTER_PWD);
  300. PreparedStatement ps = master.prepareStatement(
  301. "SELECT db_url, db_account, db_pwd FROM tenant_info WHERE id=?")) {
  302. ps.setLong(1, tenantId);
  303. try (ResultSet rs = ps.executeQuery()) {
  304. if (!rs.next()) throw new IllegalStateException("tenant_info id=" + tenantId + " not found");
  305. return parseJdbcUrl(rs.getString("db_url"), rs.getString("db_account"), rs.getString("db_pwd"));
  306. }
  307. }
  308. }
  309. private static Connection connect(String db, String user, String pwd) throws SQLException {
  310. return DriverManager.getConnection(
  311. "jdbc:mysql://" + HOST + ":" + PORT + "/" + db
  312. + "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true",
  313. user, pwd);
  314. }
  315. private static TenantDb parseJdbcUrl(String jdbcUrl, String user, String password) {
  316. String body = jdbcUrl.substring("jdbc:mysql://".length());
  317. int slash = body.indexOf('/');
  318. int q = body.indexOf('?');
  319. String hostPort = body.substring(0, slash);
  320. String database = body.substring(slash + 1, q > 0 ? q : body.length());
  321. TenantDb t = new TenantDb();
  322. t.database = database;
  323. t.user = user;
  324. t.password = password;
  325. return t;
  326. }
  327. static class TenantDb {
  328. String database, user, password;
  329. }
  330. static class Global {
  331. String mode = "HYBRID";
  332. boolean autoStart = true;
  333. boolean qualityGate = true;
  334. }
  335. static class TaskCopy {
  336. long id;
  337. String name;
  338. String executionMode;
  339. Integer contentPers, flowPers, strict, autoStart;
  340. }
  341. static class Policy {
  342. String mode;
  343. boolean strict, autoStart, allowContent, allowFlow;
  344. boolean preferInstance() {
  345. if (strict) return true;
  346. return "INSTANCE".equals(mode) || "HYBRID".equals(mode);
  347. }
  348. boolean allowPromptFallback() {
  349. if (strict) return false;
  350. return "PROMPT".equals(mode) || "HYBRID".equals(mode);
  351. }
  352. }
  353. }