>From Michael Blow <[email protected]>:

Michael Blow has submitted this change. ( 
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21191?usp=email )

Change subject: [NO ISSUE][HYR][MISC][BP] Fix Span lifefycle / API issues
......................................................................

[NO ISSUE][HYR][MISC][BP] Fix Span lifefycle / API issues

(cherry picked from commit 0d9477e9c4a)

Ext-ref: MB-71012
Change-Id: Ie96f63263eccad854dd548bb4f32aa3fbcf64480
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21191
Reviewed-by: Michael Blow <[email protected]>
Reviewed-by: Hussain Towaileb <[email protected]>
Integration-Tests: Jenkins <[email protected]>
Tested-by: Michael Blow <[email protected]>
---
M 
hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/rewriter/runtime/SuperActivityOperatorNodePushable.java
M 
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
M 
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
A 
hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
4 files changed, 389 insertions(+), 91 deletions(-)

Approvals:
  Michael Blow: Looks good to me, but someone else must approve; Verified
  Hussain Towaileb: Looks good to me, approved
  Jenkins: Verified




diff --git 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/rewriter/runtime/SuperActivityOperatorNodePushable.java
 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/rewriter/runtime/SuperActivityOperatorNodePushable.java
index 9f4ad5d..00d2358 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/rewriter/runtime/SuperActivityOperatorNodePushable.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/rewriter/runtime/SuperActivityOperatorNodePushable.java
@@ -297,9 +297,8 @@
         for (Future<Void> task : tasks) {
             task.cancel(true);
         }
-        Span completionPoll = Span.init(TASKS_COMPLETION_POLL_SECONDS, 
TimeUnit.SECONDS);
+        Span completionPoll = Span.start(TASKS_COMPLETION_POLL_SECONDS, 
TimeUnit.SECONDS);
         while (true) {
-            completionPoll.reset();
             if (completionPoll.tryAcquireUninterruptibly(completeSemaphore)) {
                 return true;
             }
@@ -314,6 +313,7 @@
                 preCancelStackTraces.put(runningThread, 
runningThread.getStackTrace());
             }
             interruptRunningThreads(runningThreads);
+            completionPoll.reset();
         }
     }

diff --git 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
index 8c8f1ce..dc699b6 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
@@ -18,11 +18,18 @@
  */
 package org.apache.hyracks.util;

+import static 
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.REFACTORED;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;

+import org.apache.hyracks.util.annotations.AiProvenance;
+
+@AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT, 
contributionKind = REFACTORED, notes = "Renamed init() to startElapsed(), made 
ELAPSED an immutable sentinel, fixed elapsed() >= off-by-one, added 
startElapsed() with do/while Javadoc")
 public class Span {

     public static final Span INFINITE = new Span() {
@@ -83,7 +90,62 @@
         }
     };

-    public static final Span ELAPSED = start(0, TimeUnit.NANOSECONDS);
+    public static final Span ELAPSED = new Span() {
+        @Override
+        public void reset() {
+            // no-op
+        }
+
+        @Override
+        public long getSpanNanos() {
+            return 0;
+        }
+
+        @Override
+        public long getSpan(TimeUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public boolean elapsed() {
+            return true;
+        }
+
+        @Override
+        public long elapsed(TimeUnit unit) {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public void sleep() {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public void sleep(long sleep, TimeUnit unit) {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public long remaining(TimeUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public void wait(Object monitor) {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public boolean await(CountDownLatch latch) {
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "<ELAPSED>";
+        }
+    };

     private final long spanNanos;
     private volatile long startNanos;
@@ -114,12 +176,32 @@
         return s;
     }

-    public static Span init(long span, TimeUnit unit) {
-        return new Span(span, unit);
+    /**
+     * Creates a span that is immediately elapsed, analogous to a do/while 
loop: the elapsed condition is true on the
+     * first check, triggering immediate action, after which {@link #reset()} 
begins the countdown for subsequent
+     * iterations. Contrast with {@link #start(long, TimeUnit)}, which is 
analogous to a while/do loop: the span must
+     * actually expire before the elapsed condition becomes true.
+     * <p>
+     * Typical usage: bootstrapping state that has a TTL, where the first 
fetch should happen immediately and
+     * subsequent refreshes should be rate-limited by the span duration.
+     * <pre>
+     *   Span refreshSpan = Span.startElapsed(ttlSeconds, TimeUnit.SECONDS);
+     *   while (running) {
+     *       if (refreshSpan.elapsed()) {
+     *           refresh();
+     *           refreshSpan.reset();
+     *       }
+     *   }
+     * </pre>
+     */
+    public static Span startElapsed(long span, TimeUnit unit) {
+        Span s = new Span(span, unit);
+        s.startNanos = System.nanoTime() - s.spanNanos;
+        return s;
     }

     public boolean elapsed() {
-        return elapsed(TimeUnit.NANOSECONDS) > spanNanos;
+        return elapsed(TimeUnit.NANOSECONDS) >= spanNanos;
     }

     public long elapsed(TimeUnit unit) {
@@ -239,7 +321,7 @@
         if (micros > 0) {
             builder.append(micros).append("us");
         }
-        if (nanos > 0 || builder.length() == 0) {
+        if (nanos > 0 || builder.isEmpty()) {
             builder.append(nanos).append("ns");
         }
         return builder.toString();
diff --git 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
index bee45b8..33e6cfb 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
@@ -19,9 +19,12 @@
 package org.apache.hyracks.util.annotations;

 import static org.apache.hyracks.util.annotations.AiProvenance.AiProvenances;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
 import static org.apache.hyracks.util.annotations.AiProvenance.Agent.GPT_5_3;
 import static 
org.apache.hyracks.util.annotations.AiProvenance.Agent.GPT_5_MINI;
 import static 
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.GENERATED;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.REFACTORED;
+import static org.apache.hyracks.util.annotations.AiProvenance.Provider.*;
 import static org.apache.hyracks.util.annotations.AiProvenance.Tool.CHATGPT_UI;
 import static 
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;

@@ -66,7 +69,7 @@

     Tool tool();

-    ContributionKind contributionKind();
+    ContributionKind contributionKind() default ContributionKind.GENERATED;

     String notes() default "";

@@ -99,101 +102,190 @@
         DOC_GENERATED
     }

+    @AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT, 
contributionKind = REFACTORED)
+    enum Provider {
+        OPENAI("openai", "OpenAI"),
+        ANTHROPIC("anthropic", "Anthropic"),
+        GOOGLE("google", "Google"),
+        META("meta", "Meta"),
+        MISTRAL("mistral", "Mistral"),
+        COHERE("cohere", "Cohere"),
+        XAI("xai", "xAI"),
+        DEEPSEEK("deepseek", "DeepSeek"),
+        ALIBABA("alibaba", "Alibaba"),
+        MICROSOFT("microsoft", "Microsoft"),
+        GITHUB("github", "GitHub"),
+        JETBRAINS("jetbrains", "JetBrains"),
+        FACTORY("factory", "Factory.ai"),
+        LANGCHAIN("langchain", "LangChain"),
+        LLAMAINDEX("llamaindex", "LlamaIndex"),
+        PERPLEXITY("perplexity", "Perplexity"),
+        CURSOR("cursor", "Cursor"),
+        WINDSURF("windsurf", "Windsurf"),
+        TABNINE("tabnine", "Tabnine"),
+        CODEIUM("codeium", "Codeium"),
+        AMAZON("amazon", "Amazon"),
+        CUSTOM("custom", "Custom"),
+        GENERIC("generic", "Generic"),
+        OTHER("other", "Other");
+
+        private final String id;
+        private final String displayName;
+
+        Provider(String id, String displayName) {
+            this.id = id;
+            this.displayName = displayName;
+        }
+
+        /** Returns the canonical provider identifier (e.g. "openai", 
"anthropic"). */
+        public String id() {
+            return id;
+        }
+
+        /** Human-friendly display name for the  */
+        public String displayName() {
+            return displayName;
+        }
+    }
+
     enum Agent {
         // OpenAI
-        GPT_5_4("openai", "gpt-5.4", "GPT-5.4"),
-        GPT_5_3("openai", "gpt-5.3", "GPT-5.3"),
-        GPT_5_2("openai", "gpt-5.2", "GPT-5.2"),
-        GPT_5_MINI("openai", "gpt-5-mini", "GPT-5 Mini"),
-        GPT_5_1_CODEX("openai", "gpt-5.1-codex", "GPT-5.1 Codex"),
-        GPT_5_3_CODEX("openai", "gpt-5.3-codex", "GPT-5.3 Codex"),
+        GPT_5_5(OPENAI, "gpt-5.5", "GPT-5.5"),
+        GPT_5_4(OPENAI, "gpt-5.4", "GPT-5.4"),
+        GPT_5_4_MINI(OPENAI, "gpt-5.4-mini", "GPT-5.4 Mini"),
+        GPT_5_4_NANO(OPENAI, "gpt-5.4-nano1", "GPT-5.4 Nano"),
+        GPT_5_3(OPENAI, "gpt-5.3", "GPT-5.3"),
+        GPT_5_2(OPENAI, "gpt-5.2", "GPT-5.2"),
+        GPT_5_MINI(OPENAI, "gpt-5-mini", "GPT-5 Mini"),
+
+        // =========================
+        // OpenAI — Codex
+        // =========================
+        GPT_5_1_CODEX(OPENAI, "gpt-5.1-codex", "GPT-5.1 Codex"),
+        GPT_5_2_CODEX(OPENAI, "gpt-5.2-codex", "GPT-5.2 Codex"),
+        GPT_5_3_CODEX(OPENAI, "gpt-5.3-codex", "GPT-5.3 Codex"),

         // =========================
         // OpenAI — GPT-4.x Family
         // =========================
-        GPT_4_1("openai", "gpt-4.1", "GPT-4.1"),
-        GPT_4_1_MINI("openai", "gpt-4.1-mini", "GPT-4.1 Mini"),
-        GPT_4_1_NANO("openai", "gpt-4.1-nano", "GPT-4.1 Nano"),
+        GPT_4_1(OPENAI, "gpt-4.1", "GPT-4.1"),
+        GPT_4_1_MINI(OPENAI, "gpt-4.1-mini", "GPT-4.1 Mini"),
+        GPT_4_1_NANO(OPENAI, "gpt-4.1-nano", "GPT-4.1 Nano"),

-        GPT_4O("openai", "gpt-4o", "GPT-4o"),
-        GPT_4O_MINI("openai", "gpt-4o-mini", "GPT-4o Mini"),
+        GPT_4O(OPENAI, "gpt-4o", "GPT-4o"),
+        GPT_4O_MINI(OPENAI, "gpt-4o-mini", "GPT-4o Mini"),

         // =========================
         // OpenAI — Reasoning (o-series)
         // =========================
-        O1("openai", "o1", "o1"),
-        O1_MINI("openai", "o1-mini", "o1 Mini"),
+        O1(OPENAI, "o1", "o1"),
+        O1_MINI(OPENAI, "o1-mini", "o1 Mini"),

-        O3("openai", "o3", "o3"),
-        O3_MINI("openai", "o3-mini", "o3 Mini"),
+        O3(OPENAI, "o3", "o3"),
+        O3_MINI(OPENAI, "o3-mini", "o3 Mini"),

-        O4("openai", "o4", "o4"),
-        O4_MINI("openai", "o4-mini", "o4 Mini"),
+        O4(OPENAI, "o4", "o4"),
+        O4_MINI(OPENAI, "o4-mini", "o4 Mini"),

         // =========================
         // Anthropic — Claude Opus
         // =========================
-        CLAUDE_OPUS_4("anthropic", "claude-4-opus", "Claude Opus 4"),
-        CLAUDE_OPUS_4_6("anthropic", "claude-4-opus-4.6", "Claude Opus 4.6"),
+        CLAUDE_OPUS_4(ANTHROPIC, "claude-4-opus", "Claude Opus 4"),
+        CLAUDE_OPUS_4_7(ANTHROPIC, "claude-4-opus-4.7", "Claude Opus 4.7"),
+        CLAUDE_OPUS_4_6(ANTHROPIC, "claude-4-opus-4.6", "Claude Opus 4.6"),
+        CLAUDE_OPUS_4_6_FAST(ANTHROPIC, "claude-4-opus-4.6-fast", "Claude Opus 
4.6 Fast"),
+        CLAUDE_OPUS_4_5(ANTHROPIC, "claude-4-opus-4.5", "Claude Opus 4.5"),

-        CLAUDE_OPUS_3("anthropic", "claude-3-opus", "Claude Opus 3"),
+        CLAUDE_OPUS_3(ANTHROPIC, "claude-3-opus", "Claude Opus 3"),

         // =========================
         // Anthropic — Claude Sonnet
         // =========================
-        CLAUDE_SONNET_4("anthropic", "claude-4-sonnet", "Claude Sonnet 4"),
-        CLAUDE_SONNET_4_5("anthropic", "claude-4-sonnet-4.5", "Claude Sonnet 
4.5"),
-        CLAUDE_SONNET_4_6("anthropic", "claude-4-sonnet-4.6", "Claude Sonnet 
4.6"),
+        CLAUDE_SONNET_4(ANTHROPIC, "claude-4-sonnet", "Claude Sonnet 4"),
+        CLAUDE_SONNET_4_5(ANTHROPIC, "claude-4-sonnet-4.5", "Claude Sonnet 
4.5"),
+        CLAUDE_SONNET_4_6(ANTHROPIC, "claude-4-sonnet-4.6", "Claude Sonnet 
4.6"),

-        CLAUDE_SONNET_3("anthropic", "claude-3-sonnet", "Claude Sonnet 3"),
-        CLAUDE_SONNET_3_5("anthropic", "claude-3-5-sonnet", "Claude Sonnet 
3.5"),
+        CLAUDE_SONNET_3(ANTHROPIC, "claude-3-sonnet", "Claude Sonnet 3"),
+        CLAUDE_SONNET_3_5(ANTHROPIC, "claude-3-5-sonnet", "Claude Sonnet 3.5"),

         // =========================
         // Anthropic — Claude Haiku
         // =========================
-        CLAUDE_HAIKU_4("anthropic", "claude-4-haiku", "Claude Haiku 4"),
-        CLAUDE_HAIKU_3("anthropic", "claude-3-haiku", "Claude Haiku 3"),
+        CLAUDE_HAIKU_4(ANTHROPIC, "claude-4-haiku", "Claude Haiku 4"),
+        CLAUDE_HAIKU_4_5(ANTHROPIC, "claude-4-haiku-4.5", "Claude Haiku 4.5"),
+        CLAUDE_HAIKU_3(ANTHROPIC, "claude-3-haiku", "Claude Haiku 3"),

-        // Google
-        GEMINI_1_5_PRO("google", "gemini-1.5-pro", "Gemini 1.5 Pro"),
-        GEMINI_1_5_FLASH("google", "gemini-1.5-flash", "Gemini 1.5 Flash"),
+        // =========================
+        // Google — Gemini
+        // =========================
+        GEMINI_3_1_PRO(GOOGLE, "gemini-3.1-pro", "Gemini 3.1 Pro"),
+        GEMINI_3_FLASH(GOOGLE, "gemini-3-flash", "Gemini 3 Flash"),
+        GEMINI_2_5_PRO(GOOGLE, "gemini-2.5-pro", "Gemini 2.5 Pro"),
+        GEMINI_1_5_PRO(GOOGLE, "gemini-1.5-pro", "Gemini 1.5 Pro"),
+        GEMINI_1_5_FLASH(GOOGLE, "gemini-1.5-flash", "Gemini 1.5 Flash"),

+        // =========================
         // Meta
-        LLAMA_3_70B("meta", "llama-3-70b", "LLaMA 3 70B"),
-        LLAMA_3_8B("meta", "llama-3-8b", "LLaMA 3 8B"),
+        // =========================
+        LLAMA_3_70B(META, "llama-3-70b", "LLaMA 3 70B"),
+        LLAMA_3_8B(META, "llama-3-8b", "LLaMA 3 8B"),

+        // =========================
         // Mistral
-        MISTRAL_LARGE("mistral", "mistral-large", "Mistral Large"),
-        MISTRAL_SMALL("mistral", "mistral-small", "Mistral Small"),
-        MIXTRAL_8X7B("mistral", "mixtral-8x7b", "Mixtral 8x7B"),
+        // =========================
+        MISTRAL_LARGE(MISTRAL, "mistral-large", "Mistral Large"),
+        MISTRAL_SMALL(MISTRAL, "mistral-small", "Mistral Small"),
+        MIXTRAL_8X7B(MISTRAL, "mixtral-8x7b", "Mixtral 8x7B"),

+        // =========================
         // Cohere
-        COMMAND_R_PLUS("cohere", "command-r-plus", "Command R+"),
-        COMMAND_R("cohere", "command-r", "Command R"),
+        // =========================
+        COMMAND_R_PLUS(COHERE, "command-r-plus", "Command R+"),
+        COMMAND_R(COHERE, "command-r", "Command R"),

+        // =========================
+        // xAI
+        // =========================
+        GROK_CODE_FAST_1(XAI, "grok-code-fast-1", "Grok Code Fast 1"),
+
+        // =========================
         // OSS / local
-        DEEPSEEK_CHAT("deepseek", "deepseek-chat", "DeepSeek Chat"),
-        DEEPSEEK_CODER("deepseek", "deepseek-coder", "DeepSeek Coder"),
-        QWEN_2("alibaba", "qwen-2", "Qwen 2"),
-        PHI_3("microsoft", "phi-3", "Phi-3"),
+        // =========================
+        DEEPSEEK_CHAT(DEEPSEEK, "deepseek-chat", "DeepSeek Chat"),
+        DEEPSEEK_CODER(DEEPSEEK, "deepseek-coder", "DeepSeek Coder"),
+        QWEN_2(ALIBABA, "qwen-2", "Qwen 2"),
+        PHI_3(MICROSOFT, "phi-3", "Phi-3"),
+
+        // =========================
+        // Fine-tuned / tool-specific
+        // =========================
+        RAPTOR_MINI(GITHUB, "raptor-mini", "Raptor Mini"),
+        GOLDENEYE(GITHUB, "goldeneye", "Goldeneye"),
+
+        // =========================
+        // Droid Core / OSS-backed
+        // =========================
+        DROID_CORE_MINIMAX_M2_7(FACTORY, "droid-core-minimax-m2.7", "Droid 
Core (MiniMax M2.7)"),
+        DROID_CORE_GLM_5_1(FACTORY, "droid-core-glm-5.1", "Droid Core 
(GLM-5.1)"),
+        DROID_CORE_GLM_5(FACTORY, "droid-core-glm-5", "Droid Core (GLM-5)"),
+        DROID_CORE_KIMI_K2_6(FACTORY, "droid-core-kimi-k2.6", "Droid Core 
(Kimi K2.6)"),
+        DROID_CORE_KIMI_K2_5(FACTORY, "droid-core-kimi-k2.5", "Droid Core 
(Kimi K2.5)"),

         // Fallback
-        OTHER("other", "other", "Other");
+        OTHER(Provider.OTHER, "other", "Other");

-        private final String provider;
+        private final Provider provider;
         private final String modelId;
         private final String displayName;

-        Agent(String provider, String modelId, String displayName) {
+        Agent(Provider provider, String modelId, String displayName) {
             this.provider = provider;
             this.modelId = modelId;
             this.displayName = displayName;
         }

-        /**
-         * Returns the provider identifier (e.g. "openai", "anthropic").
-         */
-        public String provider() {
+        /** Returns the {@link Provider} for this agent. */
+        public Provider provider() {
             return provider;
         }

@@ -205,9 +297,7 @@
             return modelId;
         }

-        /**
-         * Human-friendly display name for the model.
-         */
+        /** Human-friendly display name for the model. */
         public String displayName() {
             return displayName;
         }
@@ -234,53 +324,51 @@
     enum Tool {

         // Web / Chat UIs
-        CHATGPT_UI("openai", "chatgpt-ui", "ChatGPT"),
-        CLAUDE_UI("anthropic", "claude-ui", "Claude UI"),
-        GEMINI_UI("google", "gemini-ui", "Gemini UI"),
-        PERPLEXITY("perplexity", "perplexity", "Perplexity"),
+        CHATGPT_UI(OPENAI, "chatgpt-ui", "ChatGPT"),
+        CLAUDE_UI(ANTHROPIC, "claude-ui", "Claude UI"),
+        GEMINI_UI(GOOGLE, "gemini-ui", "Gemini UI"),
+        PERPLEXITY(Provider.PERPLEXITY, "perplexity", "Perplexity"),

         // IDE integrations
-        GITHUB_COPILOT("github", "copilot", "GitHub Copilot"),
-        GEMINI_CODE_ASSIST("google", "gemini-code-assist", "Gemini Code 
Assist"),
-        CURSOR("cursor", "cursor", "Cursor"),
-        WINDSURF("windsurf", "windsurf", "Windsurf"),
-        INTELLIJ_AI_ASSISTANT("jetbrains", "ai-assistant", "JetBrains AI 
Assistant"),
-        VSCODE_AI_EXTENSION("microsoft", "vscode-ai", "VS Code AI Extension"),
+        GITHUB_COPILOT(GITHUB, "copilot", "GitHub Copilot"),
+        GEMINI_CODE_ASSIST(GOOGLE, "gemini-code-assist", "Gemini Code Assist"),
+        CURSOR(Provider.CURSOR, "cursor", "Cursor"),
+        WINDSURF(Provider.WINDSURF, "windsurf", "Windsurf"),
+        INTELLIJ_AI_ASSISTANT(JETBRAINS, "ai-assistant", "JetBrains AI 
Assistant"),
+        VSCODE_AI_EXTENSION(MICROSOFT, "vscode-ai", "VS Code AI Extension"),

         // APIs / SDK usage
-        OPENAI_API("openai", "api", "OpenAI API"),
-        ANTHROPIC_API("anthropic", "api", "Anthropic API"),
-        GOOGLE_AI_API("google", "api", "Google AI API"),
-        GENERIC_API("generic", "api", "Generic API"),
+        OPENAI_API(OPENAI, "api", "OpenAI API"),
+        ANTHROPIC_API(ANTHROPIC, "api", "Anthropic API"),
+        GOOGLE_AI_API(GOOGLE, "api", "Google AI API"),
+        GENERIC_API(GENERIC, "api", "Generic API"),

         // Agent / orchestration platforms
-        FACTORY("factory", "factory-ai", "Factory.ai"),
-        LANGCHAIN("langchain", "langchain", "LangChain"),
-        LLAMAINDEX("llamaindex", "llamaindex", "LlamaIndex"),
-        CUSTOM_AGENT("custom", "custom-agent", "Custom Agent Runtime"),
+        FACTORY(Provider.FACTORY, "factory-ai", "Factory.ai"),
+        LANGCHAIN(Provider.LANGCHAIN, "langchain", "LangChain"),
+        LLAMAINDEX(Provider.LLAMAINDEX, "llamaindex", "LlamaIndex"),
+        CUSTOM_AGENT(CUSTOM, "custom-agent", "Custom Agent Runtime"),

         // CLI tools
-        OPENAI_CLI("openai", "cli", "OpenAI CLI"),
-        ANTHROPIC_CLI("anthropic", "cli", "Anthropic CLI"),
-        FACTORY_CLI("factory", "factory-cli", "Factory.ai CLI"),
+        OPENAI_CLI(OPENAI, "cli", "OpenAI CLI"),
+        ANTHROPIC_CLI(ANTHROPIC, "cli", "Anthropic CLI"),
+        FACTORY_CLI(Provider.FACTORY, "factory-cli", "Factory.ai CLI"),

         // Fallback
-        OTHER("other", "other", "Other");
+        OTHER(Provider.OTHER, "other", "Other");

-        private final String provider;
+        private final Provider provider;
         private final String id;
         private final String displayName;

-        Tool(String provider, String id, String displayName) {
+        Tool(Provider provider, String id, String displayName) {
             this.provider = provider;
             this.id = id;
             this.displayName = displayName;
         }

-        /**
-         * Returns the provider identifier (e.g. "openai", "anthropic").
-         */
-        public String provider() {
+        /** Returns the {@link Provider} for this tool. */
+        public Provider provider() {
             return provider;
         }

@@ -292,9 +380,7 @@
             return id;
         }

-        /**
-         * Human-friendly display name for the tool.
-         */
+        /** Human-friendly display name for the tool. */
         public String displayName() {
             return displayName;
         }
@@ -304,7 +390,7 @@
          * is useful for logging and tagging (for example 
"factory/factory-cli").
          */
         public String qualifiedName() {
-            return provider + "/" + id;
+            return id() + "/" + id;
         }
     }
 }
diff --git 
a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
 
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
new file mode 100644
index 0000000..04ccf58
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hyracks.util;
+
+import static 
org.apache.hyracks.util.annotations.AiProvenance.Agent.CLAUDE_SONNET_4_6;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.TEST_GENERATED;
+import static 
org.apache.hyracks.util.annotations.AiProvenance.Tool.GITHUB_COPILOT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hyracks.util.annotations.AiProvenance;
+import org.junit.Test;
+
+@AiProvenance(agent = CLAUDE_SONNET_4_6, tool = GITHUB_COPILOT, 
contributionKind = TEST_GENERATED, notes = "Tests for startElapsed(), immutable 
ELAPSED sentinel, and elapsed() >= boundary fix")
+public class SpanTest {
+
+    // --- Span.ELAPSED ---
+
+    @Test
+    public void elapsedConstant_alwaysElapsed() {
+        assertTrue(Span.ELAPSED.elapsed());
+    }
+
+    @Test
+    public void elapsedConstant_resetIsNoOp() {
+        Span.ELAPSED.reset();
+        assertTrue("ELAPSED.reset() should be a no-op; span should still be 
elapsed", Span.ELAPSED.elapsed());
+    }
+
+    @Test
+    public void elapsedConstant_remainingIsZero() {
+        assertEquals(0, Span.ELAPSED.remaining(TimeUnit.NANOSECONDS));
+        assertEquals(0, Span.ELAPSED.remaining(TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void elapsedConstant_elapsedUnitIsMaxValue() {
+        assertEquals(Long.MAX_VALUE, 
Span.ELAPSED.elapsed(TimeUnit.NANOSECONDS));
+    }
+
+    @Test
+    public void elapsedConstant_awaitReturnsFalseImmediately() throws 
InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        assertFalse(Span.ELAPSED.await(latch));
+    }
+
+    @Test
+    public void elapsedConstant_toStringIsElapsed() {
+        assertEquals("<ELAPSED>", Span.ELAPSED.toString());
+    }
+
+    // --- Span.startElapsed() ---
+
+    @Test
+    public void startElapsed_immediatelyElapsed() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        assertTrue("startElapsed span should immediately report elapsed()", 
span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_notElapsedAfterReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        span.reset();
+        assertFalse("startElapsed span should not be elapsed immediately after 
reset()", span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_elapsedAfterSpanDuration() throws 
InterruptedException {
+        Span span = Span.startElapsed(10, TimeUnit.MILLISECONDS);
+        span.reset();
+        assertFalse(span.elapsed());
+        TimeUnit.MILLISECONDS.sleep(15);
+        assertTrue("startElapsed span should be elapsed after duration has 
passed", span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_remainingIsZeroBeforeReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        assertEquals(0, span.remaining(TimeUnit.NANOSECONDS));
+    }
+
+    @Test
+    public void startElapsed_remainingIsPositiveAfterReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        span.reset();
+        assertTrue("remaining should be positive after reset", 
span.remaining(TimeUnit.MILLISECONDS) > 0);
+    }
+
+    // --- elapsed() boundary: >= ensures span is elapsed at exactly spanNanos 
---
+
+    @Test
+    public void start_elapsedAtExactlySpanDuration() {
+        // A span of 0ns should be immediately elapsed (>= 0)
+        Span span = Span.start(0, TimeUnit.NANOSECONDS);
+        assertTrue("0ns span should be elapsed immediately (elapsed >= 
spanNanos)", span.elapsed());
+    }
+
+    // --- Span.INFINITE sanity ---
+
+    @Test
+    public void infiniteConstant_neverElapsed() {
+        assertFalse(Span.INFINITE.elapsed());
+    }
+
+    @Test
+    public void infiniteConstant_remainingIsMaxValue() {
+        assertEquals(Long.MAX_VALUE, 
Span.INFINITE.remaining(TimeUnit.NANOSECONDS));
+    }
+}

--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21191?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://asterix-gerrit.ics.uci.edu/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: asterixdb
Gerrit-Branch: phoenix
Gerrit-Change-Id: Ie96f63263eccad854dd548bb4f32aa3fbcf64480
Gerrit-Change-Number: 21191
Gerrit-PatchSet: 3
Gerrit-Owner: Michael Blow <[email protected]>
Gerrit-Reviewer: Ali Alsuliman <[email protected]>
Gerrit-Reviewer: Anon. E. Moose #1000171
Gerrit-Reviewer: Hussain Towaileb <[email protected]>
Gerrit-Reviewer: Jenkins <[email protected]>
Gerrit-Reviewer: Michael Blow <[email protected]>
Gerrit-Reviewer: Ritik Raj <[email protected]>

Reply via email to