>From Michael Blow <[email protected]>:

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

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

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

- Fix elapsed() to use >= instead of >, which caused spans to require
  spanNanos + 1ns before reporting as elapsed (off-by-one bug)
- Introduce startElapsed() which initializes startNanos so the span
  is immediately elapsed. startElapsed() is analogous to a do/while
  loop: elapsed on first check, then reset() begins the countdown;
  start() is while/do
- Make ELAPSED an immutable sentinel (like INFINITE) with reset() as
  no-op, preventing accidental mutation of the shared constant
- Use Span.start() (not startElapsed()) for genuine countdowns where
  pre-expiry would be dangerous

Ext-ref: MB-71012
Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21186
Reviewed-by: Hussain Towaileb <[email protected]>
Reviewed-by: Michael Blow <[email protected]>
Tested-by: Jenkins <[email protected]>
---
M 
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
A 
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
3 files changed, 618 insertions(+), 5 deletions(-)

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




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 1a40360..e244f6e 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,10 +18,17 @@
  */
 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 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() {
@@ -82,7 +89,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;
@@ -93,7 +155,6 @@

     private Span(long span, TimeUnit unit) {
         spanNanos = unit.toNanos(span);
-        reset();
     }

     public void reset() {
@@ -109,11 +170,37 @@
     }

     public static Span start(long span, TimeUnit unit) {
-        return new Span(span, unit);
+        Span s = new Span(span, unit);
+        s.reset();
+        return s;
+    }
+
+    /**
+     * 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) {
@@ -196,7 +283,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
new file mode 100644
index 0000000..33e6cfb
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
@@ -0,0 +1,396 @@
+/*
+ * 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.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to record AI provenance metadata for a program element.
+ *
+ * <p>This annotation may be applied to types, methods, constructors and fields
+ * to indicate that the element was generated or assisted by an AI model. It
+ * records which {@link Agent} (model), which {@link Tool} (invocation
+ * surface) and the kind of contribution via {@link ContributionKind}.</p>
+ *
+ * <p>The annotation is repeatable using the {@link AiProvenances} container
+ * which allows multiple provenance records to be attached to the same
+ * program element (for example: initial draft generated by one model and
+ * later refinement by another).</p>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * import static org.apache.hyracks.util.annotations.AiProvenance.Agent.*;
+ * import static org.apache.hyracks.util.annotations.AiProvenance.Tool.*;
+ * import static 
org.apache.hyracks.util.annotations.AiProvenance.ContributionKind.*;
+ *
+ * {@literal @}AiProvenance(agent = GPT_5_MINI, tool = OPENAI_API, 
contributionKind = GENERATED,
+ *                 notes = "Initial PoC helper generated by GPT-5 Mini")
+ * public class ExampleGeneratedClass { ... }
+ * </pre>
+ *
+ */
+@AiProvenance(agent = GPT_5_3, tool = CHATGPT_UI, contributionKind = 
GENERATED, notes = "Initial implementation generated via GPT-5.3 (browser)")
+@AiProvenance(agent = GPT_5_MINI, tool = GITHUB_COPILOT, contributionKind = 
GENERATED, notes = "Refinements / Javadocs generated via GPT-5 Mini (GitHub 
Copilot)")
+@Retention(RetentionPolicy.SOURCE)
+@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, 
ElementType.FIELD })
+@Repeatable(AiProvenances.class)
+public @interface AiProvenance {
+    Agent agent();
+
+    Tool tool();
+
+    ContributionKind contributionKind() default ContributionKind.GENERATED;
+
+    String notes() default "";
+
+    enum ContributionKind {
+        /**
+         * The element was fully generated by an AI model.
+         */
+        GENERATED,
+
+        /**
+         * The element was assisted or suggested by an AI model and requires
+         * human review or modification.
+         */
+        ASSISTED,
+
+        /**
+         * The element was refactored or rewritten by an AI model (not newly
+         * generated from scratch).
+         */
+        REFACTORED,
+
+        /**
+         * The element is a test that was generated by an AI model.
+         */
+        TEST_GENERATED,
+
+        /**
+         * The element is documentation that was generated by an AI model.
+         */
+        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_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_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"),
+
+        O3(OPENAI, "o3", "o3"),
+        O3_MINI(OPENAI, "o3-mini", "o3 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_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"),
+
+        // =========================
+        // 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_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_4_5(ANTHROPIC, "claude-4-haiku-4.5", "Claude Haiku 4.5"),
+        CLAUDE_HAIKU_3(ANTHROPIC, "claude-3-haiku", "Claude Haiku 3"),
+
+        // =========================
+        // 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"),
+
+        // =========================
+        // Mistral
+        // =========================
+        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"),
+
+        // =========================
+        // 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"),
+
+        // =========================
+        // 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(Provider.OTHER, "other", "Other");
+
+        private final Provider provider;
+        private final String modelId;
+        private final String displayName;
+
+        Agent(Provider provider, String modelId, String displayName) {
+            this.provider = provider;
+            this.modelId = modelId;
+            this.displayName = displayName;
+        }
+
+        /** Returns the {@link Provider} for this agent. */
+        public Provider provider() {
+            return provider;
+        }
+
+        /**
+         * Returns a canonical model identifier useful for telemetry and
+         * analytics (for example "gpt-5.4" or "claude-4-opus-4.6").
+         */
+        public String modelId() {
+            return modelId;
+        }
+
+        /** Human-friendly display name for the model. */
+        public String displayName() {
+            return displayName;
+        }
+    }
+
+    /**
+     * Container annotation for repeatable {@link AiProvenance} entries.
+     *
+     * <p>Kept with RUNTIME retention to allow tools that read compiled class
+     * files to discover provenance entries. The primary {@link AiProvenance}
+     * annotation is SOURCE-retained; the container is provided to satisfy the
+     * repeatable contract when tools choose to materialize the annotations at
+     * runtime.</p>
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, 
ElementType.FIELD })
+    @interface AiProvenances {
+        /**
+         * The contained provenance entries.
+         */
+        AiProvenance[] value();
+    }
+
+    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(Provider.PERPLEXITY, "perplexity", "Perplexity"),
+
+        // IDE integrations
+        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"),
+
+        // Agent / orchestration platforms
+        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(Provider.FACTORY, "factory-cli", "Factory.ai CLI"),
+
+        // Fallback
+        OTHER(Provider.OTHER, "other", "Other");
+
+        private final Provider provider;
+        private final String id;
+        private final String displayName;
+
+        Tool(Provider provider, String id, String displayName) {
+            this.provider = provider;
+            this.id = id;
+            this.displayName = displayName;
+        }
+
+        /** Returns the {@link Provider} for this tool. */
+        public Provider provider() {
+            return provider;
+        }
+
+        /**
+         * Returns an identifier for the specific tool or integration
+         * (for example "api", "chatgpt-ui" or "copilot").
+         */
+        public String id() {
+            return id;
+        }
+
+        /** Human-friendly display name for the tool. */
+        public String displayName() {
+            return displayName;
+        }
+
+        /**
+         * Returns a compact qualified name composed of provider and id which
+         * is useful for logging and tagging (for example 
"factory/factory-cli").
+         */
+        public String qualifiedName() {
+            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/+/21186?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: trinity
Gerrit-Change-Id: I34f8fcf03329ea292c6b2637274c1c1c7c4f1462
Gerrit-Change-Number: 21186
Gerrit-PatchSet: 3
Gerrit-Owner: Michael Blow <[email protected]>
Gerrit-Reviewer: Hussain Towaileb <[email protected]>
Gerrit-Reviewer: Ian Maxon <[email protected]>
Gerrit-Reviewer: Jenkins <[email protected]>
Gerrit-Reviewer: Michael Blow <[email protected]>
Gerrit-Reviewer: Ritik Raj <[email protected]>
Gerrit-CC: Anon. E. Moose #1000171

Reply via email to