/* * Copyright (C) 2026 KofTwentyTwo */ package com.kof22.agentadmin.bootstrap; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; import com.kof22.agentcore.store.AgentMigrations; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; import org.testcontainers.containers.PostgreSQLContainer; /******************************************************************************* * Disposable acceptance infrastructure; the application runs from its ordinary distribution. ******************************************************************************/ public final class CarlPackagedVisualFixture { private final PostgreSQLContainer database = new PostgreSQLContainer<>("postgres:16-alpine"); private final Path distribution; private final Path temporary; private NativeVisualIdentity identity; private HttpsServer proxy; private Process child; private CarlQBitsVisualFixture qbits; private int nativePort; private String origin; private boolean closed; private final boolean dashboards; private CarlPackagedVisualFixture(Path distribution, boolean dashboards) throws Exception { this.distribution = distribution.toAbsolutePath(); this.dashboards = dashboards; temporary = Files.createTempDirectory("kof22-packaged-browser-"); } private void start() throws Exception { database.start(); var source = NativeDatabases.source(database.getJdbcUrl(), database.getUsername(), database.getPassword()); AgentMigrations.migrate(source); try(var connection = source.getConnection(); var sql = connection.createStatement()) { DatabaseBootstrap.initializeReader(connection, "browser_reader", "synthetic-reader"); for(String view : java.util.List.of("bill","vendor","work","account","property","artifact","calendar","transaction","budget","tax","calendar_connection", "import_review", "plan", "plan_step", "native_plan_step", "native_calendar_operation", "member", "debt", "cash_plan", "calendar_operation", "financial_goal", "financing_offer", "tax_property", "rental_property", "rental_unit", "rental_source", "rent_due", "rent_application", "expense", "expense_actual", "expense_settlement", "portfolio_move", "draft_revision", "reminder_observation", "manual_transaction", "preference", "rental_review", "rental_review_component", "rental_review_share", "tax_reference", "tax_alternative", "rental_shock", "balance_selection", "rental_baseline_selection", "plan_effect")) { sql.execute("GRANT SELECT ON carl_"+view+"_view TO browser_reader"); } sql.execute("INSERT INTO carl_household(id,name,display_zone) VALUES(1,'Synthetic Household','America/Chicago')"); sql.execute("INSERT INTO carl_member(id,household_id,principal,label,can_manage) VALUES(1,1,'alice','Synthetic Alice',true),(2,1,'bob','Synthetic Bob',true)"); sql.execute("INSERT INTO carl_permission(member_id,domain,details) SELECT m.id,d,true FROM carl_member m CROSS JOIN unnest(ARRAY['BILLS','VENDORS','CALENDAR','FINANCE','TAX','SETTINGS']) d"); connection.commit(); } var carl=new com.kof22.carlai.domain.CarlService(source,java.time.Clock.systemUTC()); var finance=new com.kof22.carlai.domain.FinancialRecords(carl); long account=finance.createAccount("alice","Synthetic Checking","CASH","USD",true,java.math.BigDecimal.ONE,"PRIVATE","Synthetic browser acceptance evidence"); new com.kof22.carlai.domain.MonarchImportWorkflow(carl).mapAccount("alice","Checking",account); finance.importBalances("alice",java.util.UUID.randomUUID(),"Date,Balance,Account\n2026-09-01,2000.00,Checking\n",java.util.Map.of("Checking",account),false); carl.importBills("alice",java.util.UUID.randomUUID(),"Synthetic bills","source_id,vendor,description,amount,currency,due_date,status,visibility\nfixture,Synthetic utility,Electric service,125.25,USD,2026-09-30,UNPAID,FAMILY\n"); long vendor=carl.createVendor("alice","Synthetic Plumber","PLUMBING",null,false,"FAMILY","Synthetic history: inspection requested, no price agreed"); long vendorWork=carl.createWorkItem("alice",vendor,"Repair tap","VENDOR_RESPONSE",java.time.LocalDate.of(2026,10,1),"Synthetic inquiry awaiting reply","FAMILY"); new com.kof22.carlai.domain.BudgetRecords(carl).create("alice",java.util.UUID.randomUUID(),"Synthetic grocery budget","PRIVATE","Groceries",java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2026,9,30),new java.math.BigDecimal("300.00"),"USD","Synthetic reviewed category plan"); long card=finance.createAccount("alice","Synthetic Card","CREDIT_CARD","USD",false,java.math.BigDecimal.ONE,"PRIVATE","Synthetic statement"); var debtPlans=new com.kof22.carlai.domain.DebtPlans(carl); debtPlans.terms("alice",card,java.time.LocalDate.of(2026,9,1),new java.math.BigDecimal("1000.00"),new java.math.BigDecimal("100.00"),java.math.BigDecimal.ZERO,new java.math.BigDecimal("0.24"),java.math.BigDecimal.ZERO,"Synthetic statement"); debtPlans.paymentProfile("alice",card,java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2026,9,15),new java.math.BigDecimal("100.00"),new java.math.BigDecimal("100.00"),com.kof22.carlai.domain.FinancialPlanning.Strategy.AVALANCHE,"Synthetic payment schedule"); long comparison=debtPlans.compare(com.kof22.carlai.domain.CarlService.Scope.privateFor("alice"),java.util.UUID.randomUUID(),java.time.LocalDate.of(2026,9,1),"USD",new java.math.BigDecimal("100.00"),12,"Synthetic budget after reserves"); var lifecycle=new com.kof22.carlai.domain.PlanLifecycle(carl); long plan=lifecycle.create("alice",java.util.UUID.randomUUID(),comparison,"Synthetic debt plan","Synthetic human selection"); lifecycle.step("alice",plan,1,java.util.UUID.randomUUID(),"Review statement",1,java.time.LocalDate.of(2026,9,30),"At home",null,"Synthetic assigned task"); lifecycle.agree("alice",plan,2,"Synthetic agreement; no financial execution"); var cash=new com.kof22.carlai.domain.CashPlans(carl); cash.create("alice","Synthetic September cash forecast","PRIVATE","Synthetic source attestations",new com.kof22.carlai.domain.CashPlans.Assumptions("USD",java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2026,9,30),new java.math.BigDecimal("1000.00"),new java.math.BigDecimal("200.00"),new java.math.BigDecimal("500.00"),true,true,true,true,true,true)); new com.kof22.carlai.domain.FinancialGoals(carl).create("alice","Synthetic debt freedom goal","DEBT_FREEDOM",1,"PRIVATE","Explicit synthetic owner priority"); var offer=new com.kof22.carlai.domain.FinancingScenarios.Offer("synthetic","USD",new java.math.BigDecimal("1000.00"),java.math.BigDecimal.ZERO,new java.math.BigDecimal("25.00"),new java.math.BigDecimal("100.00"),12,java.util.List.of(new com.kof22.carlai.domain.FinancialPlanning.Rate(1,new java.math.BigDecimal("0.24"))),com.kof22.carlai.domain.FinancingScenarios.Promotion.none(),com.kof22.carlai.domain.FinancingScenarios.Evidence.HYPOTHETICAL); new com.kof22.carlai.domain.FinancingOffers(carl).create("alice","Synthetic financing assumption","PRIVATE","REFINANCE",offer,java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2026,9,30),java.time.LocalDate.of(2026,9,15),"Unsecured assumption; not verified","Synthetic fixture only"); var rentals=new com.kof22.carlai.domain.RentalRecords(carl); long property=rentals.createProperty("alice",java.util.UUID.randomUUID(),"Synthetic rental house","PRIVATE","Synthetic deed facts",new com.kof22.carlai.domain.RentalRecords.PropertyValues("USD","Chester, Illinois",null,null,null,null,null,null,null,null,null,null,null)); rentals.createUnit("alice",java.util.UUID.randomUUID(),property,"Synthetic unit","PRIVATE","Synthetic lease",new com.kof22.carlai.domain.RentalRecords.UnitValues("Unit A",new java.math.BigDecimal("900.00"),java.time.LocalDate.of(2026,1,1),java.time.LocalDate.of(2026,12,31),"OCCUPIED")); rentals.rentDue("alice",java.util.UUID.randomUUID(),property,null,java.time.LocalDate.of(2026,9,1),new java.math.BigDecimal("900.00"),"PRIVATE","Synthetic scheduled rent"); new com.kof22.carlai.domain.ExpenseRecords(carl).create("alice",java.util.UUID.randomUUID(),"Synthetic power schedule","PRIVATE","Synthetic committed power amount",new com.kof22.carlai.domain.ExpenseRecords.Schedule("USD",com.kof22.carlai.domain.ExpenseForecast.Cadence.MONTHLY,java.time.LocalDate.of(2026,9,30),null,new java.math.BigDecimal("125.00"),java.util.Map.of(),com.kof22.carlai.domain.ExpenseForecast.Kind.EXPENSE,com.kof22.carlai.domain.ExpenseForecast.Basis.COMMITTED,null)); cash.create("alice","Synthetic purchase alternatives forecast","PRIVATE","Synthetic scope and reserve commitments",new com.kof22.carlai.domain.CashPlans.Assumptions("USD",java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2027,3,1),new java.math.BigDecimal("1000.00"),new java.math.BigDecimal("200.00"),new java.math.BigDecimal("500.00"),true,true,true,true,true,true)); var storeTerms=new com.kof22.carlai.domain.FinancingScenarios.Offer("synthetic-store","USD",new java.math.BigDecimal("400.00"),java.math.BigDecimal.ZERO,java.math.BigDecimal.ZERO,new java.math.BigDecimal("100.00"),4,java.util.List.of(new com.kof22.carlai.domain.FinancialPlanning.Rate(1,java.math.BigDecimal.ZERO),new com.kof22.carlai.domain.FinancialPlanning.Rate(5,new java.math.BigDecimal("0.24"))),new com.kof22.carlai.domain.FinancingScenarios.Promotion(com.kof22.carlai.domain.FinancingScenarios.PromotionKind.TRUE_ZERO,4,java.math.BigDecimal.ZERO,true),com.kof22.carlai.domain.FinancingScenarios.Evidence.VERIFIED_TERMS); new com.kof22.carlai.domain.FinancingOffers(carl).create("alice","Synthetic zero-interest furniture terms","PRIVATE","PURCHASE_FINANCE",storeTerms,java.time.LocalDate.of(2026,9,1),java.time.LocalDate.of(2026,10,1),java.time.LocalDate.of(2026,10,15),"Synthetic no-security terms; approval not claimed","Synthetic human-reviewed store terms"); identity = new NativeVisualIdentity(temporary); var store = java.security.KeyStore.getInstance("PKCS12"); try(var input = Files.newInputStream(temporary.resolve("synthetic-issuer.p12"))) { store.load(input, "synthetic-only".toCharArray()); } var keys = javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()); keys.init(store, "synthetic-only".toCharArray()); var tls = javax.net.ssl.SSLContext.getInstance("TLS"); tls.init(keys.getKeyManagers(), null, null); proxy = HttpsServer.create(new InetSocketAddress("127.0.0.1", 0), 0); proxy.setHttpsConfigurator(new HttpsConfigurator(tls)); proxy.setExecutor(java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()); origin = "https://localhost:" + proxy.getAddress().getPort(); try(var socket = new ServerSocket(0)) { nativePort = socket.getLocalPort(); } new com.kof22.carlai.domain.RentalRecords(carl).createProperty("alice",java.util.UUID.randomUUID(),"Synthetic second rental house","PRIVATE","Synthetic second title",new com.kof22.carlai.domain.RentalRecords.PropertyValues("USD","Chester, Illinois",java.math.BigDecimal.ONE,null,null,null,null,null,null,null,null,null,null)); carl.generateDraft(com.kof22.carlai.domain.CarlService.Scope.privateFor("alice"),java.util.UUID.randomUUID(),vendorWork,"FOLLOW_UP"); if(dashboards) { var seed = CarlDashboardVisualSeed.seed(carl); System.out.println("PACKAGED_DASHBOARDS=" + new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(seed)); } var properties = new Properties(); properties.setProperty("kof22.agent.name", "Carl AI"); properties.setProperty("kof22.agent.persona-path", distribution.resolve("prompts/PERSONA.md").toString()); properties.setProperty("kof22.agent.deployment.mode", "TEST"); properties.setProperty("kof22.agent.policy.profile", "PERSONAL"); properties.setProperty("kof22.agent.policy.owner", "James Maes"); properties.setProperty("kof22.agent.memory.enabled", "false"); properties.setProperty("kof22.agent.db.url", database.getJdbcUrl()); properties.setProperty("kof22.agent.db.username", database.getUsername()); properties.setProperty("kof22.agent.db.password", database.getPassword()); properties.setProperty("kof22.agent.qqq.db-username", "browser_reader"); properties.setProperty("kof22.agent.qqq.db-password", "synthetic-reader"); properties.setProperty("kof22.agent.qqq.host", "127.0.0.1"); properties.setProperty("kof22.agent.qqq.port", Integer.toString(nativePort)); properties.setProperty("kof22.agent.qqq.auth-mode", "oidc-bearer"); properties.setProperty("kof22.agent.qqq.public-origin", origin); properties.setProperty("kof22.agent.qqq.oidc.issuer", identity.issuer); properties.setProperty("kof22.agent.qqq.oidc.audience", "native-admin"); properties.setProperty("kof22.agent.qqq.oidc.client-id", "synthetic-client"); properties.setProperty("kof22.agent.rbac.users.alice", "OPERATOR"); properties.setProperty("kof22.agent.rbac.users.bob", "OPERATOR"); properties.setProperty("kof22.agent.slack.enabled", "false"); properties.setProperty("kof22.agent.mcp.enabled", "false"); properties.setProperty("kof22.agent.client-api.enabled", "true"); properties.setProperty("kof22.agent.client-api.issuer", identity.issuer); properties.setProperty("kof22.agent.client-api.audience", "carl-family"); boolean liveModel = "true".equals(System.getenv("CARL_PREVIEW_LIVE_MODEL")); if(liveModel && (System.getenv("ANTHROPIC_API_KEY") == null || System.getenv("ANTHROPIC_API_KEY").isBlank())) { throw new IllegalStateException("Live synthetic preview requires ANTHROPIC_API_KEY"); } properties.setProperty("kof22.agent.anthropic-api-key", liveModel ? "${ANTHROPIC_API_KEY}" : "synthetic-unused"); properties.setProperty("kof22.agent.anthropic-base-url", liveModel ? "https://api.anthropic.com" : "http://127.0.0.1:9"); properties.setProperty("kof22.agent.model.id", "claude-sonnet-5"); Path configuration = temporary.resolve("agent.properties"); Files.createFile(configuration, java.nio.file.attribute.PosixFilePermissions.asFileAttribute( java.nio.file.attribute.PosixFilePermissions.fromString("rw-------"))); try(var output = Files.newOutputStream(configuration)) { properties.store(output, "Disposable local browser acceptance"); } if("true".equals(System.getenv("CARL_PREVIEW_QBITS"))) { qbits = CarlQBitsVisualFixture.start(temporary.resolve("qbits")); } startChild(); var http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); proxy.createContext("/", exchange -> { try { var request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + nativePort + exchange.getRequestURI())) .timeout(Duration.ofSeconds(30)); exchange.getRequestHeaders().forEach((name, values) -> { if(!Set.of("host", "connection", "content-length", "upgrade", "http2-settings").contains(name.toLowerCase(java.util.Locale.ROOT))) { values.forEach(value -> request.header(name, value)); } }); request.method(exchange.getRequestMethod(), HttpRequest.BodyPublishers.ofByteArray(exchange.getRequestBody().readAllBytes())); var response = http.send(request.build(), HttpResponse.BodyHandlers.ofByteArray()); response.headers().map().forEach((name, values) -> { if(!Set.of("transfer-encoding", "content-length", "connection").contains(name)) { exchange.getResponseHeaders().put(name, values); } }); exchange.sendResponseHeaders(response.statusCode(), response.body().length == 0 ? -1 : response.body().length); try(var output = exchange.getResponseBody()) { output.write(response.body()); } } catch(Exception failure) { exchange.sendResponseHeaders(502, -1); exchange.close(); } }); proxy.createContext("/synthetic-calendar/", exchange -> { String calendarProperties; if(exchange.getRequestMethod().equals("PROPFIND")) { calendarProperties = ""; } else if(exchange.getRequestMethod().equals("REPORT")) { String calendar = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Carl//Synthetic//EN\r\nBEGIN:VEVENT\r\nUID:synthetic-browser-agenda\r\nDTSTAMP:20260901T000000Z\r\nDTSTART:20260930T150000Z\r\nDTEND:20260930T160000Z\r\nSUMMARY:Synthetic household review\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; calendarProperties = "\"synthetic-v1\""; } else { exchange.sendResponseHeaders(405, -1); exchange.close(); return; } String href = exchange.getRequestMethod().equals("REPORT") ? "/synthetic-calendar/shared.ics" : "/synthetic-calendar/"; byte[] response = ("" + href + "" + calendarProperties + "HTTP/1.1 200 OK").getBytes(java.nio.charset.StandardCharsets.UTF_8); exchange.getResponseHeaders().set("Content-Type", "application/xml"); exchange.sendResponseHeaders(207, response.length); try(var output = exchange.getResponseBody()) { output.write(response); } }); var reminder = new java.util.concurrent.atomic.AtomicReference(); proxy.createContext("/synthetic-reminders/", exchange -> { String body = ""; int status = 404; if(exchange.getRequestMethod().equals("PROPFIND")) { status = 207; body = "/synthetic-reminders/HTTP/1.1 200 OK"; } else if(exchange.getRequestMethod().equals("PUT")) { reminder.set(new String(exchange.getRequestBody().readAllBytes(), java.nio.charset.StandardCharsets.UTF_8)); status = 201; } else if(exchange.getRequestMethod().equals("GET") && reminder.get() != null) { body = reminder.get().replace("STATUS:NEEDS-ACTION", "STATUS:COMPLETED").replace("END:VTODO", "COMPLETED:20260930T120000Z\r\nEND:VTODO"); status = 200; } exchange.getResponseHeaders().set("ETag", "\"synthetic-reminder-v1\""); byte[] bytes=body.getBytes(java.nio.charset.StandardCharsets.UTF_8); exchange.sendResponseHeaders(status, bytes.length==0 ? -1 : bytes.length); try(var output=exchange.getResponseBody()) { output.write(bytes); } }); proxy.start(); System.out.println("PACKAGED_UI=" + origin); System.out.println("PACKAGED_APPLICATION=ordinary-app.jar-with-lib"); } private void startChild() throws Exception { var builder = new ProcessBuilder(Path.of(System.getProperty("java.home"), "bin", "java").toString(), "-Djavax.net.ssl.trustStore=" + temporary.resolve("synthetic-trust.p12"), "-Djavax.net.ssl.trustStorePassword=synthetic-only", "-Dqqq.logger.logSessionId.disabled=true", "-Dqqq.rdbms.logSQL=false", "-jar", distribution.resolve("app.jar").toString(), temporary.resolve("agent.properties").toString()); builder.directory(temporary.toFile()); builder.environment().keySet().removeIf(name -> name.startsWith("KOF22_") || name.startsWith("CARL_") || Set.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS").contains(name)); builder.environment().put("CARL_CALDAV_EVENTS_COLLECTION", origin + "/synthetic-calendar/"); builder.environment().put("CARL_CALDAV_EVENTS_AUDIENCE_MEMBERS", "1,2"); builder.environment().put("CARL_CALDAV_REMINDERS_COLLECTION", origin + "/synthetic-reminders/"); builder.environment().put("CARL_CALDAV_REMINDERS_AUDIENCE_MEMBERS", "1"); builder.environment().put("CARL_CALDAV_STANDING_PRINCIPAL", "alice"); builder.environment().put("CARL_CALDAV_USERNAME", "synthetic"); builder.environment().put("CARL_CALDAV_PASSWORD", "synthetic-only"); if(qbits != null) { builder.environment().putAll(qbits.environment()); } child = builder.redirectErrorStream(true).redirectOutput(ProcessBuilder.Redirect.appendTo(temporary.resolve("application.log").toFile())).start(); var http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(1)).build(); long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(35); while(System.nanoTime() < deadline) { if(!child.isAlive()) { throw new IllegalStateException("Packaged child startup failed; private log at " + temporary.resolve("application.log")); } try { var response = http.send(HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + nativePort + "/health/ready")).timeout(Duration.ofSeconds(1)).GET().build(), HttpResponse.BodyHandlers.discarding()); if(response.statusCode() == 200) { return; } } catch(java.io.IOException ignored) { // Readiness can refuse connections while the child is still starting. } Thread.sleep(100); } throw new IllegalStateException("Packaged application readiness timed out"); } private void stopChild() throws Exception { if(child == null) { return; } child.destroy(); if(!child.waitFor(20, TimeUnit.SECONDS)) { child.destroyForcibly(); throw new IllegalStateException("Packaged application did not stop cleanly"); } } private synchronized void close() { if(closed) { return; } closed = true; RuntimeException cleanupFailure = null; try { stopChild(); } catch(Exception failure) { cleanupFailure = new IllegalStateException("Packaged child cleanup failed", failure); } if(proxy != null) { proxy.stop(0); } if(identity != null) { identity.close(); } if(qbits != null) { try { qbits.close(); } catch(Exception failure) { if(cleanupFailure == null) { cleanupFailure = new IllegalStateException("Packaged QBits cleanup failed", failure); } else { cleanupFailure.addSuppressed(failure); } } } try { database.stop(); } catch(RuntimeException failure) { if(cleanupFailure == null) { cleanupFailure = failure; } else { cleanupFailure.addSuppressed(failure); } } try(var paths = Files.walk(temporary)) { for(var path : paths.sorted(java.util.Comparator.reverseOrder()).toList()) { Files.deleteIfExists(path); } } catch(java.io.IOException failure) { if(cleanupFailure == null) { cleanupFailure = new IllegalStateException("Private fixture cleanup failed", failure); } else { cleanupFailure.addSuppressed(failure); } } if(cleanupFailure != null) { throw cleanupFailure; } } /******************************************************************************* * Starts disposable browser acceptance infrastructure and accepts lifecycle commands. ******************************************************************************/ public static void main(String[] arguments) throws Exception { System.setProperty("qqq.logger.logSessionId.disabled", "true"); System.setProperty("qqq.rdbms.logSQL", "false"); var fixture = new CarlPackagedVisualFixture(Path.of(arguments[0]), arguments.length > 1 && arguments[1].equals("--dashboards")); Runtime.getRuntime().addShutdownHook(new Thread(fixture::close)); try { fixture.start(); } catch(Exception failure) { fixture.close(); throw failure; } try(var input = new java.io.BufferedReader(new java.io.InputStreamReader(System.in))) { String command; while((command = input.readLine()) != null) { if(command.equals("restart")) { fixture.stopChild(); fixture.startChild(); System.out.println("PACKAGED_RESTARTED"); } else if(command.equals("revoke-dashboard-access") && fixture.dashboards) { try(var c = NativeDatabases.source(fixture.database.getJdbcUrl(), fixture.database.getUsername(), fixture.database.getPassword()).getConnection(); var sql = c.createStatement()) { c.setAutoCommit(false); sql.execute("UPDATE carl_permission SET details=false WHERE member_id=1 AND domain='FINANCE'"); c.commit(); } System.out.println("PACKAGED_DASHBOARDS_REVOKED"); } else if(command.equals("qualify-qbits-event") && fixture.qbits != null) { var source = NativeDatabases.source(fixture.database.getJdbcUrl(), fixture.database.getUsername(), fixture.database.getPassword()); var evidence = fixture.qbits.qualifyIndexRefresh(source, fixture.temporary.resolve("application.log")); System.out.println("PACKAGED_QBITS_EVENT=" + new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(evidence)); } else if(command.equals("close")) { break; } } } fixture.close(); } }