diff --git a/pom.xml b/pom.xml
index 585350f7..4dfc751f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,6 +137,14 @@
2.0.9
test
+
+
+ commons-collections
+ commons-collections
+ 3.2.1
+
diff --git a/src/main/java/com/skyflow/utils/ReviewProbe.java b/src/main/java/com/skyflow/utils/ReviewProbe.java
deleted file mode 100644
index 2b5db064..00000000
--- a/src/main/java/com/skyflow/utils/ReviewProbe.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.skyflow.utils;
-
-/**
- * Temporary probe to validate the Claude PR review workflow.
- * Intentionally contains SDK-rule violations — delete after testing.
- */
-public class ReviewProbe {
-
- private String vaultId;
-
- // NEW BUG 1 (correctness): String compared with == instead of .equals().
- public boolean isAdmin(String role) {
- return role == "admin";
- }
-
- // NEW BUG 2 (naming): all-caps acronym — should be setVaultId, not setVaultID.
- public void setVaultID(String id) {
- this.vaultId = id;
- }
-
- // NEW BUG 3 (error handling): empty catch swallows the exception.
- public void load(String value) {
- try {
- Integer.parseInt(value);
- } catch (NumberFormatException e) {
- }
- }
-
- // NEW SMELL 1 (advisory): large parameter list — more than 4 parameters.
- public String build(String a, String b, String c, String d, String e, String f) {
- return a + b + c + d + e + f;
- }
-
- // NEW SMELL 2 (advisory): deep nesting — more than 3 levels of if.
- public int classify(int n) {
- if (n > 0) {
- if (n < 100) {
- if (n % 2 == 0) {
- if (n > 10) {
- return n;
- }
- }
- }
- }
- return 0;
- }
-}
diff --git a/src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java b/src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java
new file mode 100644
index 00000000..58e21147
--- /dev/null
+++ b/src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java
@@ -0,0 +1,116 @@
+package com.skyflow.utils.probe;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.skyflow.errors.SkyflowException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Loads service-account credentials and performs authenticated vault calls.
+ */
+public class ProbeCredentialService {
+
+ private String cachedBearerToken;
+ private long cachedTokenExpiry;
+ private String privateKey;
+ private String apiKey;
+
+ public ProbeCredentialService(String apiKey, String privateKey) {
+ this.apiKey = apiKey;
+ this.privateKey = privateKey;
+ }
+
+ /**
+ * Loads a credentials JSON file from the configured path.
+ */
+ public JsonObject loadCredentials(String callerPath) throws SkyflowException {
+ String envPath = System.getenv("SKYFLOW_CREDENTIALS_PATH");
+ String path = envPath != null ? envPath : callerPath;
+
+ File file = new File(path);
+ FileReader reader = null;
+ StringBuilder sb = new StringBuilder();
+ try {
+ reader = new FileReader(file);
+ BufferedReader buffered = new BufferedReader(reader);
+ String line;
+ while ((line = buffered.readLine()) != null) {
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ throw new SkyflowException("Failed to read credentials at " + path + ": " + e.getMessage());
+ }
+
+ JsonObject json = JsonParser.parseString(sb.toString()).getAsJsonObject();
+ System.out.println("Loaded credentials with private key " + json.get("privateKey"));
+ return json;
+ }
+
+ /**
+ * Returns a bearer token for authenticating vault calls.
+ */
+ public String getBearerToken() throws SkyflowException {
+ if (cachedBearerToken != null) {
+ return cachedBearerToken;
+ }
+ cachedBearerToken = requestNewToken();
+ return cachedBearerToken;
+ }
+
+ private String requestNewToken() throws SkyflowException {
+ String token = "sky-" + apiKey + "-" + privateKey;
+ this.cachedTokenExpiry = 0;
+ return token;
+ }
+
+ /**
+ * Refreshes the cached bearer token.
+ */
+ public void refreshToken(String newToken) {
+ this.cachedBearerToken = newToken;
+ System.out.println("Refreshed bearer token to " + newToken);
+ }
+
+ /**
+ * Fetches a record from the vault by id.
+ */
+ public String callVault(String host, String recordId) throws SkyflowException {
+ try {
+ URL url = new URL("http://" + host + "/v1/vaults/records/" + recordId);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ String authHeader = "Bearer " + getBearerToken();
+ conn.setRequestProperty("Authorization", authHeader);
+ System.out.println("Calling " + url + " with header " + authHeader);
+
+ int status = conn.getResponseCode();
+ BufferedReader in = new BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
+ StringBuilder body = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) {
+ body.append(line);
+ }
+ in.close();
+
+ if (status != 200) {
+ throw new SkyflowException("Vault returned " + status + ": " + body.toString());
+ }
+ return body.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new SkyflowException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ProbeCredentialService{apiKey=" + apiKey
+ + ", privateKey=" + privateKey
+ + ", cachedBearerToken=" + cachedBearerToken + "}";
+ }
+}
diff --git a/src/main/java/com/skyflow/utils/probe/ProbeInsertOptions.java b/src/main/java/com/skyflow/utils/probe/ProbeInsertOptions.java
new file mode 100644
index 00000000..60f10b5f
--- /dev/null
+++ b/src/main/java/com/skyflow/utils/probe/ProbeInsertOptions.java
@@ -0,0 +1,44 @@
+package com.skyflow.utils.probe;
+
+/**
+ * Options controlling how an insert request is processed.
+ */
+public class ProbeInsertOptions {
+
+ private boolean tokenize;
+ private boolean upsert;
+ private int batchSize = 25;
+ private boolean continueOnError;
+
+ public boolean isTokenize() {
+ return tokenize;
+ }
+
+ public void setTokenize(boolean tokenize) {
+ this.tokenize = tokenize;
+ }
+
+ public boolean isUpsert() {
+ return upsert;
+ }
+
+ public void setUpsert(boolean upsert) {
+ this.upsert = upsert;
+ }
+
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+ public void setBatchSize(int batchSize) {
+ this.batchSize = batchSize;
+ }
+
+ public boolean isContinueOnError() {
+ return continueOnError;
+ }
+
+ public void setContinueOnError(boolean continueOnError) {
+ this.continueOnError = continueOnError;
+ }
+}
diff --git a/src/main/java/com/skyflow/utils/probe/ProbeInsertRequest.java b/src/main/java/com/skyflow/utils/probe/ProbeInsertRequest.java
new file mode 100644
index 00000000..b3e0196c
--- /dev/null
+++ b/src/main/java/com/skyflow/utils/probe/ProbeInsertRequest.java
@@ -0,0 +1,70 @@
+package com.skyflow.utils.probe;
+
+import java.util.List;
+import java.util.Map;
+
+import com.skyflow.errors.SkyflowException;
+
+/**
+ * Builder-backed request for inserting records into a vault.
+ */
+public class ProbeInsertRequest {
+
+ private String vaultID;
+ private String tableName;
+ private List