From 39911460f2c1ea3ce10cc6e57c46285bdf2d0b3a Mon Sep 17 00:00:00 2001 From: Peter Isberg Date: Mon, 3 Aug 2026 20:42:58 +0200 Subject: [PATCH] chore: bump VibeTags to 1.0.0-RC10 and opt into Gemini granular rules RC8 to RC10 in the pom property, the one place both builds read it. New opt-ins: GEMINI.md plus .gemini/rules/, which the root .vibetags-roles groups into the same five role files the Claude layout uses instead of ~130 per-class files. The roles file header now names the RC10 format owner. Claude granular stays per module, the layout this repo already chose. Part of the VibeTags GA readiness sweep. Verified: mvn clean compile regenerates (log lists 5 active services), a touched source and recompile reports no changes everywhere, and CLAUDE.md, AGENTS.md and .claudeignore are byte-identical across the bump. Branched from main, not from the quality/ga-javadoc-final checkout, so nothing unrelated rides along. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013j5WQ59Jk8wwFP6ZZeyu5K --- .gemini/rules/async-test-configuration.md | 66 ++++++ .gemini/rules/async-test-detectors.md | 221 ++++++++++++++++++++ .gemini/rules/async-test-instrumentation.md | 15 ++ .gemini/rules/async-test-public-api.md | 78 +++++++ .gemini/rules/async-test-runtime-core.md | 78 +++++++ .vibetags-roles | 2 +- GEMINI.md | 142 +++++++++++++ pom.xml | 2 +- 8 files changed, 602 insertions(+), 2 deletions(-) create mode 100644 .gemini/rules/async-test-configuration.md create mode 100644 .gemini/rules/async-test-detectors.md create mode 100644 .gemini/rules/async-test-instrumentation.md create mode 100644 .gemini/rules/async-test-public-api.md create mode 100644 .gemini/rules/async-test-runtime-core.md create mode 100644 GEMINI.md diff --git a/.gemini/rules/async-test-configuration.md b/.gemini/rules/async-test-configuration.md new file mode 100644 index 00000000..c3ed719a --- /dev/null +++ b/.gemini/rules/async-test-configuration.md @@ -0,0 +1,66 @@ + +# Rules for async-test-configuration + +## Locked Status + +### se.deversity.asynctest.DetectorType +- **Reason**: Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system. + +## Mirrored — Keep In Sync + +### se.deversity.asynctest.DetectorType +- **Rule**: Free to change, but every mirror must change in the same commit. +- **Mirrors**: se.deversity.asynctest.AsyncTest, se.deversity.asynctest.AsyncTestConfig, se.deversity.asynctest.DetectorRegistry, se.deversity.asynctest.spi.LegacyDetectorFactories, META-INF/services/se.deversity.asynctest.spi.DetectorFactory +- **Reason**: A detector is only reachable from the public API when all of these agree. The enum constant is the name users type in @AsyncTest(excludes=...); the annotation attribute, the config field and its Builder default carry it through resolution; the registry constructor instantiates it; and the SPI factory plus its services entry are what detectAll loads. Adding the constant alone compiles and silently detects nothing. +- **Enforced by**: se.deversity.asynctest.spi.AllDetectorsSpiCoverageTest + +## Context & Focus + +### se.deversity.asynctest.AsyncTestConfig +- **Focus**: Maintain strict 1:1 mapping between @AsyncTest attributes, Builder fields, from(AsyncTest), build() logic, and DetectorRegistry +- **Avoid**: mutable state — this class must remain immutable after construction + +### se.deversity.asynctest.DetectorRegistry +- **Focus**: Each new detector requires exactly three steps in this class: (1) a final field declaration, (2) conditional construction in the constructor keyed on the config flag, (3) an analyzeAll() call in the correct phase block. All three steps must be added together. +- **Avoid**: partial patterns — a field without construction or analysis silently skips detection + +## Core Functionality + +### se.deversity.asynctest.AsyncTestConfig +- **Sensitivity**: Critical +- **Note**: Adding a new detector requires synchronized changes across six places: @AsyncTest attribute, AsyncTestConfig field, Builder default, from(AsyncTest) call chain, build() detectAll/excludes blocks, and DetectorRegistry constructor. + +## Immutable Type +- **Rule**: These types are immutable. Never introduce non-final fields, setters, or mutating methods. + +### se.deversity.asynctest.AsyncTestConfig +- **Note**: Immutable snapshot of @AsyncTest parameters to ensure thread safety. + +### se.deversity.asynctest.Preset +- **Note**: Enum constants — JVM guarantees structural immutability. Internal enabled-set is captured at class init. + +## Feature Flag Gate +- **Rule**: This code is gated behind a feature flag. Preserve the flag check. Never assume the flag is always active. + +### se.deversity.asynctest.AsyncTestConfig.enableBenchmarking +- **Flag**: 'async-test.benchmarking.enabled' (default: false) + +### se.deversity.asynctest.AsyncTestConfig.licenseMockMode +- **Flag**: 'license.mock.mode' (default: false) + +## Thread-Safety Guarantee + +### se.deversity.asynctest.DetectorRegistry +- **Strategy**: SYNCHRONIZED +- **Note**: Guards conditional access to internal detector initialization and phase blocks. + +## Contract-Frozen Signature + +### se.deversity.asynctest.AsyncTest +- **Constraint**: You may change internal logic, but MUST NOT modify the method name, parameters, return type, or checked exceptions. +- **Reason**: Public annotation API used directly in user test methods. Attribute names, types, and defaults are part of the stable public API — any change is a breaking change for all consumers. + +## Public API Surface Protection +- **Rule**: Exposes public API. Preserve signature, Javadoc, and behavior without breaking backwards or source compatibility. +- **Applies to**: `se.deversity.asynctest.AsyncTest`, `se.deversity.asynctest.Preset` + diff --git a/.gemini/rules/async-test-detectors.md b/.gemini/rules/async-test-detectors.md new file mode 100644 index 00000000..ff887c4f --- /dev/null +++ b/.gemini/rules/async-test-detectors.md @@ -0,0 +1,221 @@ + +# Rules for async-test-detectors + +## Performance Constraints + +### se.deversity.asynctest.diagnostics.SiteCapture +- **Rule**: Optimal complexity required. O(n^2) is forbidden on hot paths. +- **Constraint**: Called from detector recordAccess paths; do not allocate when a site is already captured for a given key. + +## Test-Driven Requirements +- **Rule**: Changes MUST be accompanied by a matching test update. +- **Coverage Goal**: 80% +- **Frameworks**: JUNIT_5 + +### se.deversity.asynctest.diagnostics.CompletableFutureBlockingCallbackDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/CompletableFutureBlockingCallbackDetectorTest.java + +### se.deversity.asynctest.diagnostics.CompletableFutureObtrudeDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/CompletableFutureObtrudeDetectorTest.java + +### se.deversity.asynctest.diagnostics.DaemonThreadHygieneDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/DaemonThreadHygieneDetectorTest.java + +### se.deversity.asynctest.diagnostics.FileChannelPositionRaceDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/FileChannelPositionRaceDetectorTest.java + +### se.deversity.asynctest.diagnostics.FinalFieldMutationDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/FinalFieldMutationDetectorTest.java + +### se.deversity.asynctest.diagnostics.HighContentionAtomicDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/HighContentionAtomicDetectorTest.java + +### se.deversity.asynctest.diagnostics.JdbcConnectionSharedDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/JdbcConnectionSharedDetectorTest.java + +### se.deversity.asynctest.diagnostics.LazyConstantMisuseDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/LazyConstantMisuseDetectorTest.java + +### se.deversity.asynctest.diagnostics.LockUpgradeDeadlockDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/LockUpgradeDeadlockDetectorTest.java + +### se.deversity.asynctest.diagnostics.NonAtomicConcurrentMapUpdateDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/NonAtomicConcurrentMapUpdateDetectorTest.java + +### se.deversity.asynctest.diagnostics.NotifyWithoutMonitorDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/NotifyWithoutMonitorDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedByteBufferDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedByteBufferDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedCharsetCoderDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedCharsetCoderDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedChecksumDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedChecksumDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedDeflaterDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedDeflaterDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedIteratorDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedIteratorDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedJsonMapperReconfigDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedJsonMapperReconfigDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedKdfDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedKdfDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedMessageDigestDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedMessageDigestDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedSecureRandomDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedSecureRandomDetectorTest.java + +### se.deversity.asynctest.diagnostics.SharedStatefulCryptoDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SharedStatefulCryptoDetectorTest.java + +### se.deversity.asynctest.diagnostics.SpuriousWakeupDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/SpuriousWakeupDetectorTest.java + +### se.deversity.asynctest.diagnostics.ThisEscapeDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/ThisEscapeDetectorTest.java + +### se.deversity.asynctest.diagnostics.ThreadLocalRandomMisuseDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/ThreadLocalRandomMisuseDetectorTest.java + +### se.deversity.asynctest.diagnostics.TryLockMisuseDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/TryLockMisuseDetectorTest.java + +### se.deversity.asynctest.diagnostics.WeakHashMapSharedDetector +- **Test Location**: src/test/java/se/deversity/asynctest/diagnostics/WeakHashMapSharedDetectorTest.java + +## Thread-Safety Guarantee + +### se.deversity.asynctest.diagnostics.CompletableFutureBlockingCallbackDetector +- **Strategy**: OTHER +- **Note**: ThreadLocal tracks active callbacks; ConcurrentHashMap stores violations. + +### se.deversity.asynctest.diagnostics.CompletableFutureObtrudeDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap stores state per CF instance. + +### se.deversity.asynctest.diagnostics.DaemonThreadHygieneDetector +- **Strategy**: OTHER +- **Note**: Per-thread access map is a ConcurrentHashMap; first-registration-wins via putIfAbsent. + +### se.deversity.asynctest.diagnostics.FileChannelPositionRaceDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet() and track only implicit-position accessors. + +### se.deversity.asynctest.diagnostics.FinalFieldMutationDetector +- **Strategy**: OTHER +- **Note**: Per-field state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.HighContentionAtomicDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; counters are LongAdder; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.JdbcConnectionSharedDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap-backed JDBC-resource tracking; per-resource State holds ConcurrentHashMap.newKeySet() for accessing threads. + +### se.deversity.asynctest.diagnostics.LazyConstantMisuseDetector +- **Strategy**: OTHER +- **Note**: Per-constant state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id sets are ConcurrentHashMap.newKeySet(); reports are synchronized lists. + +### se.deversity.asynctest.diagnostics.LockUpgradeDeadlockDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap tracks read lock ownership and violations. + +### se.deversity.asynctest.diagnostics.NonAtomicConcurrentMapUpdateDetector +- **Strategy**: OTHER +- **Note**: Per (map,key) state in a ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.NotifyWithoutMonitorDetector +- **Strategy**: SYNCHRONIZED +- **Note**: Attempts list mutated under a single intrinsic monitor on the list itself; sampling Thread.holdsLock requires no locking. + +### se.deversity.asynctest.diagnostics.SharedByteBufferDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name and operation sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedCharsetCoderDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedChecksumDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedDeflaterDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedIteratorDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedJsonMapperReconfigDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; using-thread sets are ConcurrentHashMap.newKeySet(); violating mutations recorded in a CopyOnWriteArrayList. + +### se.deversity.asynctest.diagnostics.SharedKdfDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedMessageDigestDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedSecureRandomDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with double-check (get-then-computeIfAbsent) hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SharedStatefulCryptoDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with double-check (get-then-computeIfAbsent) hot path; thread-id/name sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.SpuriousWakeupDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap stores state per monitor instance. + +### se.deversity.asynctest.diagnostics.ThisEscapeDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; escape descriptions and observer-thread sets are ConcurrentHashMap.newKeySet(); the completed flag is volatile. + +### se.deversity.asynctest.diagnostics.ThreadLocalRandomMisuseDetector +- **Strategy**: OTHER +- **Note**: Per-instance state in ConcurrentHashMap with get-then-computeIfAbsent hot path; misusing-thread sets are ConcurrentHashMap.newKeySet(). + +### se.deversity.asynctest.diagnostics.TryLockMisuseDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap tracks tryLock attempts, results, and unlock violations. + +### se.deversity.asynctest.diagnostics.WeakHashMapSharedDetector +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap-backed instance tracking; per-instance State holds ConcurrentHashMap.newKeySet() for thread ids/names. + +## Security-Critical Code +- **Rule**: This code is security-critical. Do not weaken security properties. Every change must be explicitly reviewed for security impact. + +### se.deversity.asynctest.diagnostics.SharedMessageDigestDetector +- **Aspect**: cryptography (hash integrity / MAC / signature state) + +### se.deversity.asynctest.diagnostics.SharedSecureRandomDetector +- **Aspect**: cryptography (RNG quality) + +### se.deversity.asynctest.diagnostics.SharedStatefulCryptoDetector +- **Aspect**: cryptography (confidentiality / integrity / authenticity state) + +## Immutable Type + +### se.deversity.asynctest.diagnostics.SiteCapture.Site +- **Rule**: This type is immutable. Never introduce non-final fields, setters, or mutating methods. +- **Note**: Java record — fields are final by language; types are all primitives or String. + +## Public API Surface Protection + +### se.deversity.asynctest.diagnostics.SiteCapture.Site +- **Rule**: Exposes public API. Preserve signature, Javadoc, and behavior without breaking backwards or source compatibility. + diff --git a/.gemini/rules/async-test-instrumentation.md b/.gemini/rules/async-test-instrumentation.md new file mode 100644 index 00000000..5e2bfb36 --- /dev/null +++ b/.gemini/rules/async-test-instrumentation.md @@ -0,0 +1,15 @@ + +# Rules for async-test-instrumentation + +## Core Functionality + +### se.deversity.asynctest.agent.AsyncTestAgent +- **Sensitivity**: Critical +- **Note**: The INSTALLED gate must stay at-most-once per JVM: every entry point (premain, agentmain, selfAttach) races on the same compareAndSet, and a second transformer would double-weave field accessors and double-count every access. premain installs without retransformation because classes are woven as they load; agentmain must keep RETRANSFORMATION + disableClassFormatChanges(), which is only safe while the Advice stays a method-entry prologue that adds no fields, methods or interfaces. Nothing may throw out of premain — an exception there aborts JVM startup. The Premain-Class / Agent-Class manifest entries live in this module's jar, which is why attaching uses -javaagent:async-test-agent.jar. + +## Contract-Frozen Signature + +### se.deversity.asynctest.agent.AgentOptions +- **Constraint**: You may change internal logic, but MUST NOT modify the method name, parameters, return type, or checked exceptions. +- **Reason**: The class is package-private but the agentArgs grammar it parses is public surface: users type it on the -javaagent: command line. Key names (includes/excludes/debug), the comma-or-semicolon separator, the bare-token continuation that lets one key carry several values, and case-insensitive key matching are all part of that contract — changing any of them breaks existing launch scripts silently. Parsing must stay total: it is called from premain, where a thrown exception aborts JVM startup, so unknown keys are ignored and malformed input degrades to the default instrument-everything behaviour rather than failing. + diff --git a/.gemini/rules/async-test-public-api.md b/.gemini/rules/async-test-public-api.md new file mode 100644 index 00000000..9c711595 --- /dev/null +++ b/.gemini/rules/async-test-public-api.md @@ -0,0 +1,78 @@ + +# Rules for async-test-public-api + +## Exclusion Rule + +### se.deversity.asynctest.NoopAsyncTestListener +This element is strictly excluded from AI context. Do not reference it. + +## Performance Constraints + +### se.deversity.asynctest.spi.adapters.LegacyDetectorAdapter +- **Rule**: Optimal complexity required. O(n^2) is forbidden on hot paths. +- **Constraint**: analyze() does Method.getMethod + invoke each call; only invoked once per round per detector, not on the hot recordAccess path. If profiling shows reflection overhead, cache the Method handles in the constructor. + +## Legacy Compatibility Bridge + +### se.deversity.asynctest.spi.adapters.LegacyDetectorAdapter +- **Rule**: Compatibility bridge. Do not attempt to modernize, elegant-ize, or refactor structural patterns. Only modify internal business logic as explicitly requested. + +## Contract-Frozen Signature +- **Constraint**: You may change internal logic, but MUST NOT modify the method name, parameters, return type, or checked exceptions. + +### se.deversity.asynctest.AsyncAssert +- **Reason**: Public assertion utility API for AsyncTest consumers. awaitUntil() and capture() are used directly in user test code — method signatures and semantics must not change without a major version bump. + +### se.deversity.asynctest.AsyncTestListener +- **Reason**: Public SPI interface for observing async-test lifecycle events. Method signatures are part of the stable API — implementors bind to these exact names and parameter types. + +### se.deversity.asynctest.AsyncTestListenerRegistry +- **Reason**: Public API for registering and unregistering AsyncTestListener instances. register(), unregister(), clearAll(), and fireXxx() methods are called by user code and infrastructure — signatures must not change. + +### se.deversity.asynctest.report.Formatter +- **Reason**: Public formatter SPI. format(List) signature must not change — built-in formatters and user-provided lambdas bind to this exact type. + +### se.deversity.asynctest.spi.Detector +- **Reason**: Public SPI interface. type(), analyze(), onTestStart(), and onTestEnd() signatures are part of the stable extension contract — implementors bind to these exact names and parameter types. + +### se.deversity.asynctest.spi.DetectorFactory +- **Reason**: Public SPI interface for ServiceLoader-based detector discovery. type(), isEnabledFor(), and create() signatures are part of the stable factory contract — implementors bind to these exact names and parameter types. + +## Public API Surface Protection +- **Rule**: Exposes public API. Preserve signature, Javadoc, and behavior without breaking backwards or source compatibility. +- **Applies to**: `se.deversity.asynctest.AsyncAssert`, `se.deversity.asynctest.AsyncTestListener`, `se.deversity.asynctest.AsyncTestListenerRegistry`, `se.deversity.asynctest.report.Formatter`, `se.deversity.asynctest.report.JsonFormatter`, `se.deversity.asynctest.report.MarkdownFormatter`, `se.deversity.asynctest.report.Violation`, `se.deversity.asynctest.spi.Detector`, `se.deversity.asynctest.spi.DetectorFactory`, `se.deversity.asynctest.spi.DetectorRegistry` + +## Idempotency Guarantee +- **Rule**: These operations are idempotent. Calling them multiple times must produce the same result as calling them once. + +### se.deversity.asynctest.AsyncTestListenerRegistry.Registration.close() +- **Reason**: Guarded by the `closed` volatile flag; second close() returns early before touching the registry. Covered by `registrationClose_isIdempotent` test. + +### se.deversity.asynctest.AsyncTestListenerRegistry.clearAll() +- **Reason**: List.clear() on an already-empty list is a no-op; repeated calls have identical observable effect (empty registry). + +### se.deversity.asynctest.AsyncTestListenerRegistry.unregister(se.deversity.asynctest.AsyncTestListener) +- **Reason**: Backed by List.remove which is a no-op when the listener is absent; second call returns false but produces no observable side effect. + +### se.deversity.asynctest.spi.DetectorRegistry.analyzeAll() +- **Reason**: Each Detector.analyze() must return the same violations for the same observed state (the SPI contract). Calling analyzeAll() N times on a quiescent registry yields N identical lists; do not introduce stateful side-effects in analyze(). + +## Polymorphic Extension Pattern +- **Pattern**: STRATEGY_PATTERN +- **Rule**: Open for extension, closed for modification. Use strategy or visitor subclasses instead of changing these files. +- **Applies to**: `se.deversity.asynctest.report.Formatter`, `se.deversity.asynctest.spi.Detector` + +## Immutable Type +- **Rule**: These types are immutable. Never introduce non-final fields, setters, or mutating methods. + +### se.deversity.asynctest.report.Violation +- **Note**: Java record — fields are final by language. Collection fields are deep-copied to immutable views in the canonical constructor. + +### se.deversity.asynctest.spi.DetectorRegistry +- **Note**: Effectively immutable after build() — the EnumMap is populated only in the private constructor and never mutated thereafter; safe to publish to multiple threads and read-only views over an EnumMap populated once at construction. + +## Input Sanitization +- **Target Filters**: XSS +- **Rule**: Run raw input strings through approved sanitizers. +- **Applies to**: `se.deversity.asynctest.report.JUnitXmlReportListener.onStructuredReport(java.lang.String,se.deversity.asynctest.diagnostics.IssueSeverity,java.lang.String)#report`, `se.deversity.asynctest.report.JsonReportListener.onStructuredReport(java.lang.String,se.deversity.asynctest.diagnostics.IssueSeverity,java.lang.String)#report` + diff --git a/.gemini/rules/async-test-runtime-core.md b/.gemini/rules/async-test-runtime-core.md new file mode 100644 index 00000000..f534d6ac --- /dev/null +++ b/.gemini/rules/async-test-runtime-core.md @@ -0,0 +1,78 @@ + +# Rules for async-test-runtime-core + +## Security Audit Requirements +When modifying these elements, audit for: +- Thread Safety issues +- **Applies to**: `se.deversity.asynctest.AsyncTestContext` + +### se.deversity.asynctest.runner.ConcurrencyRunner +- Resource Leaks + +## Core Functionality +- **Sensitivity**: Critical + +### se.deversity.asynctest.AsyncTestContext +- **Note**: ThreadLocal install/uninstall must always be symmetric. A leak propagates stale detector state across test invocations and causes false positives or missed detections. + +### se.deversity.asynctest.extension.AsyncTestInvocationInterceptor +- **Note**: invocation.skip() is intentional — ConcurrencyRunner owns the full N×M execution and must never call invocation.proceed(). Restoring proceed() would run the test body once outside the CyclicBarrier, bypassing all detectors. + +### se.deversity.asynctest.runner.ConcurrencyRunner +- **Note**: Core stress-test execution engine. The CyclicBarrier pattern forces maximum thread contention. Timeout logic and AsyncTestContext install/uninstall are carefully calibrated — subtle changes introduce flaky tests or missed detector activations. + +## Thread-Safety Guarantee + +### se.deversity.asynctest.AsyncTestContext +- **Strategy**: THREAD_LOCAL +- **Note**: CURRENT ThreadLocal maintains context per active test thread symmetrically. + +### se.deversity.asynctest.runner.ConcurrencyRunner +- **Strategy**: OTHER +- **Note**: Coordinates concurrency using CyclicBarrier to maximize thread contention. + +### se.deversity.asynctest.runner.LicenseGuard +- **Strategy**: OTHER +- **Note**: ConcurrentHashMap.computeIfAbsent guarantees at-most-once gate execution per fingerprint under contention; volatile announce flags collapse the GRANTED/CI banner to once-per-JVM. + +## Public API Surface Protection +- **Rule**: Exposes public API. Preserve signature, Javadoc, and behavior without breaking backwards or source compatibility. +- **Applies to**: `se.deversity.asynctest.AsyncTestContext`, `se.deversity.asynctest.AsyncTestContext.sharedCryptographyDetector()`, `se.deversity.asynctest.AsyncTestContext.sharedMessageDigestDetector()`, `se.deversity.asynctest.extension.AsyncTestExtension` + +## Idempotency Guarantee +- **Rule**: These operations are idempotent. Calling them multiple times must produce the same result as calling them once. + +### se.deversity.asynctest.AsyncTestContext.uninstall() +- **Reason**: ThreadLocal.remove() is documented as a no-op when the thread has no value set; the install/uninstall symmetry rule (CLAUDE.md) tolerates extra uninstalls. ConcurrencyRunner relies on this in its outermost-finally cleanup. + +### se.deversity.asynctest.runner.LicenseGuard.check(se.deversity.asynctest.AsyncTestConfig) +- **Reason**: ConcurrentHashMap.computeIfAbsent guarantees the underlying gate.check fires at most once per Fingerprint; repeat calls return immediately. Denied results consistently throw SecurityException for the same fingerprint. + +## Access Restrictions + +### se.deversity.asynctest.AsyncTestContext.install(se.deversity.asynctest.AsyncTestContext) +- **Allowed Callers**: [se.deversity.asynctest.runner.ConcurrencyRunner] + +## Load-Bearing Oddity +- **Rule**: This looks removable but is deliberate. Refactor only while the invariant holds. + +### se.deversity.asynctest.extension.AsyncTestInvocationInterceptor.interceptTestTemplateMethod(org.junit.jupiter.api.extension.InvocationInterceptor.Invocation,org.junit.jupiter.api.extension.ReflectiveInvocationContext,org.junit.jupiter.api.extension.ExtensionContext) +- **Invariant**: This method calls invocation.skip() and never invocation.proceed(). +- **Breaks if changed**: Someone 'fixes' the apparently-dropped invocation by calling proceed(). The test body then runs once on the JUnit thread, outside the CyclicBarrier and outside AsyncTestContext, so no detector observes it — and because that single run usually passes, the suite goes green while every concurrency check has silently stopped running. + +### se.deversity.asynctest.runner.ConcurrencyRunner.execute(org.junit.jupiter.api.extension.ReflectiveInvocationContext,se.deversity.asynctest.AsyncTestConfig) +- **Invariant**: The timeoutAlreadyReported flag, and the per-step guarded cleanup in the finally block, are both deliberate. A pre-round deadline check throws an error that has already been through timeoutError(), and each cleanup step is wrapped in its own try so one failure cannot suppress the next. +- **Breaks if changed**: The flag is removed as redundant — the catch block then sends the same error through timeoutError() a second time, producing two onTimeout callbacks and two copies of every report for one timeout. Or the cleanup steps are merged into one try, at which point a failing AsyncTestContext.uninstall() skips the livelock snapshot and leaks context into the next test. + +## Contract-Frozen Signature + +### se.deversity.asynctest.extension.AsyncTestExtension +- **Constraint**: You may change internal logic, but MUST NOT modify the method name, parameters, return type, or checked exceptions. +- **Reason**: JUnit 5 TestTemplateInvocationContextProvider SPI. The two overridden methods (supportsTestTemplate, provideTestTemplateInvocationContexts) must preserve their exact signatures as mandated by JUnit. + +## Security-Critical Code + +### se.deversity.asynctest.runner.LicenseGuard +- **Rule**: This code is security-critical. Do not weaken security properties. Every change must be explicitly reviewed for security impact. +- **Aspect**: authorization + diff --git a/.vibetags-roles b/.vibetags-roles index 9a2ae4f3..8792c51a 100644 --- a/.vibetags-roles +++ b/.vibetags-roles @@ -1,4 +1,4 @@ -# VibeTags role routing (vibetags 1.0.0-RC8, se.deversity.vibetags.processor.internal.RoleConfig). +# VibeTags role routing (vibetags 1.0.0-RC10, se.deversity.vibetags.processor.internal.RoleConfig). # # Presence of this file switches granular rule generation from "one file per annotated # class" to "one file per role". Without it, .claude/rules/ would hold ~130 files — one diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..8373d927 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,142 @@ +# GEMINI.md + +AI guardrails for Google Gemini, generated from source annotations by +[VibeTags](https://github.com/PIsberg/vibetags). The region between the +VIBETAGS-START and VIBETAGS-END markers is regenerated on every compile; +never hand-edit inside it. Per-element detail lives in `.gemini/rules/`, +indexed from the block below. + + + +# AUTO-GENERATED AI RULES +# Generated by VibeTags | https://github.com/PIsberg/vibetags +# Do not edit manually. + +## 🧠 CORE FUNCTIONALITY (CHANGE WITH EXTREME CAUTION) +The following elements are well-tested core components. Make changes with extreme caution. + +- `se.deversity.asynctest.agent.AsyncTestAgent`: Sensitivity: Critical. Note: The INSTALLED gate must stay at-most-once per JVM: every entry point (premain, agentmain, selfAttach) races on the same compareAndSet, and a second transformer would double-weave field accessors and double-count every access. premain installs without retransformation because classes are woven as they load; agentmain must keep RETRANSFORMATION + disableClassFormatChanges(), which is only safe while the Advice stays a method-entry prologue that adds no fields, methods or interfaces. Nothing may throw out of premain — an exception there aborts JVM startup. The Premain-Class / Agent-Class manifest entries live in this module's jar, which is why attaching uses -javaagent:async-test-agent.jar. + +## Scoped Rules Index +Detailed per-element guardrails live in scoped rule files that load automatically when you open the matching source file. Consult the referenced file before modifying an element: + +- `se.deversity.asynctest.agent.AgentOptions` → `.gemini/rules/async-test-instrumentation.md` +- `se.deversity.asynctest.agent.AsyncTestAgent` → `.gemini/rules/async-test-instrumentation.md` + + +# AUTO-GENERATED AI RULES +# Generated by VibeTags | https://github.com/PIsberg/vibetags +# Do not edit manually. + +## 🧠 CORE FUNCTIONALITY (CHANGE WITH EXTREME CAUTION) +The following elements are well-tested core components. Make changes with extreme caution. + +- `se.deversity.asynctest.analysis.StaticPinningScanner`: Sensitivity: High. Note: The whole module is this one class plus ASM, and ArchitectureTest pins both directions: nothing here may reference the library, and asm may not leak out of here. Keep the analysis one-directional — if the scanner starts needing the runner or a detector, that is a design question, not a dependency to add. The asymmetry in the findings is deliberate and must be preserved: monitor depth is tracked within a single method body only, so cross-method synchronization yields false negatives, and MONITOREXIT on exception-handler edges may undercount depth. False negatives are acceptable here; a false positive is not, because the scanner runs without executing tests and has no way to confirm a site. + +## Scoped Rules Index +Detailed per-element guardrails live in scoped rule files that load automatically when you open the matching source file. Consult the referenced file before modifying an element: + +- `se.deversity.asynctest.analysis.StaticPinningScanner` → `.gemini/rules/async-test-instrumentation.md` + + +# AUTO-GENERATED AI RULES +# Generated by VibeTags | https://github.com/PIsberg/vibetags +# Do not edit manually. + +## LOCKED FILES (DO NOT EDIT) +- `se.deversity.asynctest.DetectorType`: Each enum constant requires synchronized changes in five places: (1) @AsyncTest attribute, (2) AsyncTestConfig field, (3) AsyncTestConfig.Builder default, (4) both branches of AsyncTestConfig.build() (detectAll block + excludes block), and (5) DetectorRegistry constructor. Adding a value here in isolation breaks the system. + +## 🛡️ MANDATORY SECURITY AUDITS +When proposing edits or writing code for the following files, you MUST perform a security review before outputting the final code. You must explicitly state in your response that you have audited the changes for the required vulnerabilities. + +File: `se.deversity.asynctest.AsyncTestContext` +Critical Vulnerabilities to Prevent: +- Thread Safety issues + +File: `se.deversity.asynctest.runner.ConcurrencyRunner` +Critical Vulnerabilities to Prevent: +- Thread Safety issues +- Resource Leaks + + +## 🚫 IGNORED ELEMENTS (EXCLUDE FROM CONTEXT) +Do not reference, suggest changes to, or include the following in completions or answers. + +- `se.deversity.asynctest.NoopAsyncTestListener` + +## 🧠 CORE FUNCTIONALITY (CHANGE WITH EXTREME CAUTION) +The following elements are well-tested core components. Make changes with extreme caution. + +- `se.deversity.asynctest.AsyncTestConfig`: Sensitivity: Critical. Note: Adding a new detector requires synchronized changes across six places: @AsyncTest attribute, AsyncTestConfig field, Builder default, from(AsyncTest) call chain, build() detectAll/excludes blocks, and DetectorRegistry constructor. +- `se.deversity.asynctest.AsyncTestContext`: Sensitivity: Critical. Note: ThreadLocal install/uninstall must always be symmetric. A leak propagates stale detector state across test invocations and causes false positives or missed detections. +- `se.deversity.asynctest.extension.AsyncTestInvocationInterceptor`: Sensitivity: Critical. Note: invocation.skip() is intentional — ConcurrencyRunner owns the full N×M execution and must never call invocation.proceed(). Restoring proceed() would run the test body once outside the CyclicBarrier, bypassing all detectors. +- `se.deversity.asynctest.runner.ConcurrencyRunner`: Sensitivity: Critical. Note: Core stress-test execution engine. The CyclicBarrier pattern forces maximum thread contention. Timeout logic and AsyncTestContext install/uninstall are carefully calibrated — subtle changes introduce flaky tests or missed detector activations. + +## 🔐 SECURITY-CRITICAL CODE +The following elements are security-critical. AI must not weaken security properties. Any change must be reviewed for security impact. + +- `se.deversity.asynctest.diagnostics.SharedMessageDigestDetector`: Security-critical code [cryptography (hash integrity / MAC / signature state)]. Do not weaken security properties. Flag any change for security review. +- `se.deversity.asynctest.diagnostics.SharedSecureRandomDetector`: Security-critical code [cryptography (RNG quality)]. Do not weaken security properties. Flag any change for security review. +- `se.deversity.asynctest.diagnostics.SharedStatefulCryptoDetector`: Security-critical code [cryptography (confidentiality / integrity / authenticity state)]. Do not weaken security properties. Flag any change for security review. +- `se.deversity.asynctest.runner.LicenseGuard`: Security-critical code [authorization]. Do not weaken security properties. Flag any change for security review. + +## Scoped Rules Index +Detailed per-element guardrails live in scoped rule files that load automatically when you open the matching source file. Consult the referenced file before modifying an element: + +- `se.deversity.asynctest.AsyncAssert` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.AsyncTest` → `.gemini/rules/async-test-configuration.md` +- `se.deversity.asynctest.AsyncTestConfig` → `.gemini/rules/async-test-configuration.md` +- `se.deversity.asynctest.AsyncTestContext` → `.gemini/rules/async-test-runtime-core.md` +- `se.deversity.asynctest.AsyncTestListener` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.AsyncTestListenerRegistry` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.AsyncTestListenerRegistry.Registration` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.DetectorRegistry` → `.gemini/rules/async-test-configuration.md` +- `se.deversity.asynctest.DetectorType` → `.gemini/rules/async-test-configuration.md` +- `se.deversity.asynctest.NoopAsyncTestListener` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.Preset` → `.gemini/rules/async-test-configuration.md` +- `se.deversity.asynctest.benchmark.BenchmarkComparator` → `.gemini/rules/async-test-instrumentation.md` +- `se.deversity.asynctest.benchmark.BenchmarkRecorder` → `.gemini/rules/async-test-instrumentation.md` +- `se.deversity.asynctest.diagnostics.CompletableFutureBlockingCallbackDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.CompletableFutureObtrudeDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.DaemonThreadHygieneDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.FileChannelPositionRaceDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.FinalFieldMutationDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.HighContentionAtomicDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.JdbcConnectionSharedDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.LazyConstantMisuseDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.LockUpgradeDeadlockDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.NonAtomicConcurrentMapUpdateDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.NotifyWithoutMonitorDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedByteBufferDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedCharsetCoderDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedChecksumDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedDeflaterDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedIteratorDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedJsonMapperReconfigDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedKdfDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedMessageDigestDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedSecureRandomDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SharedStatefulCryptoDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SiteCapture` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SiteCapture.Site` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.SpuriousWakeupDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.ThisEscapeDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.ThreadLocalRandomMisuseDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.TryLockMisuseDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.diagnostics.WeakHashMapSharedDetector` → `.gemini/rules/async-test-detectors.md` +- `se.deversity.asynctest.extension.AsyncTestExtension` → `.gemini/rules/async-test-runtime-core.md` +- `se.deversity.asynctest.extension.AsyncTestInvocationInterceptor` → `.gemini/rules/async-test-runtime-core.md` +- `se.deversity.asynctest.report.Formatter` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.report.JUnitXmlReportListener` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.report.JsonFormatter` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.report.JsonReportListener` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.report.MarkdownFormatter` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.report.Violation` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.runner.ConcurrencyRunner` → `.gemini/rules/async-test-runtime-core.md` +- `se.deversity.asynctest.runner.LicenseGuard` → `.gemini/rules/async-test-runtime-core.md` +- `se.deversity.asynctest.spi.Detector` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.spi.DetectorFactory` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.spi.DetectorRegistry` → `.gemini/rules/async-test-public-api.md` +- `se.deversity.asynctest.spi.adapters.LegacyDetectorAdapter` → `.gemini/rules/async-test-public-api.md` + + diff --git a/pom.xml b/pom.xml index e2976ec9..ab6dce0e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 3.2.8 2.9.3 0.11.0 - 1.0.0-RC8 + 1.0.0-RC10 3.6.0 13.9.0 4.10.3.0