floci-az
AGENTS.md814 starsChanged 21 days agoMITView on GitHub
Guidance for AI coding agents working in the floci-az repository. This file defines repository-specific operating rules for autonomous or semi-autonomous coding agents. Follow these instructions unless a maintainer explicitly tells you otherwise. floci-az is a Java-based local Azure emulator built on Quarkus. Its goal is full Azure SDK and Azure CLI compatibility through real Azure wire protocols, not convenience APIs or simplified abstractions. floci-az is the Azure counterpart of Floci (the AWS emulator) and floci-gcp. They share design patterns and…
- Reads credentials
We split this AGENTS.md into the rules it gives an agent
65 rules. About 0 are common, 33 are distinctive, and 32 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈33
Distinctive · estimate
This author's own choices. Worth a look.
32
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 29
When making changes, follow these priorities:
- Distinctive: line 34
Prefer correctness over convenience
- Distinctive: line 35
Keep changes narrow and testable
- Distinctive: line 39
Do not introduce custom endpoint shapes
- Distinctive: line 40
Do not change request or response formats for convenience
- Distinctive: line 41
Do not perform broad refactors unless the task explicitly requires them
- Very specific: line 42
Keep behavior aligned with Azure SDK expectations and existing floci-az conventions
- Very specific: line 144
Use XmlBuilder for XML responses (supports start, startAttr, selfClose, elem, raw, end)
- Very specific: line 145
Use XmlParser for XML parsing.
- Very specific: line 146
Use XmlUtils for Jackson-based serialisation of annotated model classes
- Very specific: line 158
Always use StorageFactory to obtain a StorageBackend
- Distinctive: line 159
Do not instantiate storage implementations directly inside handlers
- Distinctive: line 163
When adding storage-related behavior:
- Very specific: line 165
Update EmulatorConfig (add ServiceStorageConfig accessor)
- Very specific: line 166
Update ServicesStorageConfig in EmulatorConfig
- Very specific: line 169
Add storage mode to BannerLogger.getStorageMode()
- Very specific: line 179
Add interface method to EmulatorConfig (inner interface or new @WithDefault)
- Very specific: line 180
Add to ServicesConfig if service-scoped
- Very specific: line 181
Add to main application.yml
- Very specific: line 182
Update BannerLogger if user-visible
- Distinctive: line 183
Update documentation if user-facing
- Very specific: line 184
Follow FLOCI_AZ_* environment variable conventions (SmallRye converts .
- Very specific: line 202
Always call lifecycleManager.removeIfExists(containerName) before creating to clean up stale containers
- Very specific: line 203
Use ContainerBuilder + ContainerLifecycleManager.
- Distinctive: line 259
Always validate new implementations against the relevant SDK test suite
- Distinctive: line 260
Prefer SDK clients over raw HTTP for all validation
- Very specific: line 261
Use make test-<service> to run a specific suite
- Very specific: line 262
Use make compat-docker to run all suites against a running container
- Distinctive: line 363
If a change affects request parsing, response shape, error handling, persistence semantics, URL generation, or service enablement:
- Distinctive: line 365
Add or update automated tests
- Very specific: line 366
Prefer SDK-based verification in compatibility-tests/
- Distinctive: line 367
Check behavior across all relevant SDK languages
- Distinctive: line 368
Document intentional deviations clearly
- Very specific: line 375
Use AzureErrorResponse helper for XML-format errors (Storage services)
- Distinctive: line 382
When adding a new HTTP-based service:
- Very specific: line 385
Add *Handler.java implementing AzureServiceHandler
- Very specific: line 402
Add config interface to EmulatorConfig.ServicesConfig
- Very specific: line 403
Add YAML block to application.yml
- Distinctive: line 404
Add storage wiring if needed
- Very specific: line 407
Update docs (docs/services/<svc>.md, docs/services/index.md, mkdocs.yml, README.md)
- Distinctive: line 409
When adding a sidecar-based service, additionally:
- Distinctive: line 424
When touching a class that still uses field injection, migrate it to constructor injection if the change is small.
- Very specific: line 429
Never leave a catch block empty.
- Distinctive: line 434
Keep methods small and focused.
- Distinctive: line 435
Prefer self-explanatory code over comments.
- Distinctive: line 436
Always use braces in conditionals
- Distinctive: line 437
Follow existing project patterns before introducing new abstractions
- Distinctive: line 438
Use modern Java features when they improve clarity
- Distinctive: line 439
Use Java records for simple data carriers.
- Very specific: line 458
No wildcard imports in src/main, including static ones.
- Very specific: line 460
Import order: non-java/javax imports alphabetically, then java.* and javax.* last.
- Distinctive: line 499
No em-dashes anywhere, in any content.
- Very specific: line 503
Use JBoss Logging (Logger.getLogger(MyClass.class)) in src/main/java
- Very specific: line 508
Use LOG.infov(...) / LOG.debugv(...) for parameterised messages
- Distinctive: line 509
Keep logs structured; avoid noisy logs in hot paths
- Distinctive: line 516
Keep changes focused; avoid unrelated refactors
- Distinctive: line 518
Update docs when functionality changes
- Very specific: line 529
Do not add Co-Authored-By trailers for AI tools in commit messages.
- Very specific: line 539
Use the workflow's dry-run input to preview the next version and notes without tagging.
- Distinctive: line 546
When in doubt about Azure wire protocol behavior, refer to real sources:
- Distinctive: line 552
Do not invent protocol behavior.
- Distinctive: line 558
Do not introduce non-Azure endpoint shapes
- Very specific: line 559
Do not bypass StorageFactory for storage operations
- Very specific: line 560
Do not call dockerClient directly in new service code.
- Distinctive: line 561
Do not make sidecar startup failures fatal (they would break unit tests and environments without Docker)
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Guidance for AI coding agents working in the floci-az repository.
This file defines repository-specific operating rules for autonomous or semi-autonomous coding agents. Follow these instructions unless a maintainer explicitly tells you otherwise.
Project Overview
floci-az is a Java-based local Azure emulator built on Quarkus.
Its goal is full Azure SDK and Azure CLI compatibility through real Azure wire protocols, not convenience APIs or simplified abstractions.
floci-az is the Azure counterpart of Floci (the AWS emulator) and floci-gcp. They share design patterns and Docker infrastructure, but are independent repositories.
- Port: 4577 (all HTTP services share this single port)
- AMQP port: 5672 (Event Hubs, managed by Artemis sidecar)
- Kafka port: 9093 (Event Hubs Kafka, managed by Redpanda sidecar, opt-in)
- Stack:
- Java 25
- Quarkus 3.x
- JUnit 5 + RestAssured
- Jackson
- docker-java for sidecar container management
First Principles
When making changes, follow these priorities:
- Preserve Azure protocol compatibility
- Match Azure SDK and CLI behavior
- Reuse existing floci-az patterns
- Prefer correctness over convenience
- Keep changes narrow and testable
Critical rules:
- Do not introduce custom endpoint shapes
- Do not change request or response formats for convenience
- Do not perform broad refactors unless the task explicitly requires them
- Keep behavior aligned with Azure SDK expectations and existing floci-az conventions
Architecture
floci-az follows a layered design:
- AzureRoutingFilter: pre-matching JAX-RS filter; extracts account name and service type from the request path; dispatches to the correct
AzureServiceHandler - AzureServiceHandler: interface implemented by each service (
getServiceType(),handle()) - AzureServiceRegistry: CDI registry of all handlers; checks
isEnabled()per service - Service Handler: parses Azure protocol input, contains business logic, produces Azure-compatible responses
- StorageBackend: pluggable persistence (memory / persistent / hybrid / wal)
Core Infrastructure
EmulatorConfig: SmallRye@ConfigMapping; prefixfloci-azAzureRoutingFilter: assembles its host, account-suffix and ARM-provider dispatch tables at@PostConstructfrom every handler'sroutes(); no per-service suffix list lives in the filterServiceRoutes: the routing declaration a handler returns fromroutes()(account suffixes such as-queue, host suffixes such as.blob.core.windows.net, ARM providers such asMicrosoft.App)AzureServiceRegistry: handler discovery +isEnabled(), which delegates tohandler.enabled(serviceType)BannerLogger: startup banner listing all enabled servicesStorageBackend+StorageFactory: pluggable storageXmlBuilder: fluent XML builder with attribute support (startAttr,selfClose)XmlParser: StAX-based XML parser (no extra dependencies)XmlUtils: Jackson-based XML serialisation for structured models
Sidecar Container Infrastructure (core/docker/)
Used by services that delegate to a managed Docker container:
ContainerSpec: immutable container descriptor (record)ContainerBuilder: fluentContainerSpecbuilder (network, ports, mounts, log rotation, DNS)ContainerLifecycleManager: create/start/stop/remove containers; volume management; endpoint resolutionImageCacheService: pull-once-per-image with registry credential supportPortAllocator: thread-safe free TCP port allocationContainerDetector: detects whether floci-az is running inside DockerCurrentContainerNetworkResolver: resolves which Docker network floci-az is onDockerHostResolver: resolves the correct host for child containers to reach floci-azDockerClientProducer: CDI producer forDockerClient
Package Layout
io.floci.az.config : EmulatorConfig
io.floci.az.core : routing, registry, banner, XML utils, auth, DNS
io.floci.az.core.auth : auth pipeline, verifiers
io.floci.az.core.docker : container infrastructure
io.floci.az.core.storage : storage backends
io.floci.az.core.ui : web console sidecar (/_floci/ui), Console Contract v2 environment
io.floci.az.services.<svc> : per-service handlersTypical service structure:
services/<svc>/
*Handler.java : implements AzureServiceHandler
*Models.java : domain objects (optional)Sidecar-based services additionally have:
services/<svc>/
*ContainerManager.java : @ApplicationScoped, @PostConstruct/@PreDestroy lifecycle
*Manager.java : manages one sidecar (pull, start, health-wait, stop)
*ConfigGenerator.java : generates sidecar config file from EmulatorConfigRule: copy an existing service pattern before introducing a new one.
Azure Protocol Rules
| Service | Protocol | Path Suffix | Notes |
| Blob Storage | Azure Storage REST | /{account}/ | XML responses, Shared Key auth |
| Queue Storage | Azure Storage REST | /{account}-queue/ | XML responses |
| Table Storage | Azure Storage REST | /{account}-table/ | JSON / OData |
| Azure Functions | HTTP REST | /{account}-functions/ | ZIP deploy, HTTP trigger invoke |
| App Configuration | Azure App Config REST | /{account}-appconfig/ | JSON, ETags, labels |
| Key Vault | Azure Key Vault REST | /{account}-keyvault/ | JSON, Bearer auth challenge |
| Event Hubs | AMQP 1.0 (Artemis) | port 5672 | Direct sidecar, not proxied through 4577 |
| Event Hubs Kafka | Kafka (Redpanda) | port 9093 | Optional, direct sidecar |
Key Vault auth challenge
The azure-keyvault SDK uses ChallengeAuthPolicy: it sends a probe request without a body to elicit a 401 WWW-Authenticate response. The handler must return 401 when no Authorization header is present. Always include verify_challenge_resource=False in SDK test clients (localhost can never match .vault.azure.net).
Event Hubs
Event Hubs does not use the HTTP port for data-plane traffic. All SDK send/receive goes over AMQP 1.0 directly to the Artemis sidecar on port 5672. The /{account}-eventhub/ path at port 4577 is admin/health only.
XML / JSON Rules
- Use
XmlBuilderfor XML responses (supportsstart,startAttr,selfClose,elem,raw,end) - Use
XmlParserfor XML parsing; do not use regex or string manipulation for XML - Use
XmlUtilsfor Jackson-based serialisation of annotated model classes - JSON errors must follow Azure error structures (
{"error":{"code":"...","message":"..."}}) - Types returned from handlers must remain reflection-safe for GraalVM native image
Storage Rules
Supported storage modes: memory, persistent, hybrid, wal
Rules:
- Always use
StorageFactoryto obtain aStorageBackend - Do not instantiate storage implementations directly inside handlers
- Respect lifecycle hooks for load and flush behavior
- Event Hubs does not use
StorageBackend: it delegates entirely to the Artemis sidecar
When adding storage-related behavior:
- Update
EmulatorConfig(addServiceStorageConfigaccessor) - Update
ServicesStorageConfiginEmulatorConfig - Update main
application.yml - Wire through
StorageFactory - Add storage mode to
BannerLogger.getStorageMode()
Configuration Rules
Configuration lives under floci-az.* and maps to EmulatorConfig via SmallRye @ConfigMapping.
When adding config:
- Add interface method to
EmulatorConfig(inner interface or new@WithDefault) - Add to
ServicesConfigif service-scoped - Add to main
application.yml - Update
BannerLoggerif user-visible - Update documentation if user-facing
- Follow
FLOCI_AZ_*environment variable conventions (SmallRye converts.→_and lowercases)
Critical areas:
base-url/hostname: affects URLs returned in API responsesport: default 4577storage.mode: global default; per-service override viastorage.services.<svc>.modeservices.docker-network: shared Docker network for sidecar containersdocker.docker-host: Docker daemon socket
Sidecar Container Rules
Services that use sidecar containers (Event Hubs, Service Bus, SQL, PostgreSQL, Redis, ACR, AKS, ...) follow this pattern:
*ContainerManagerobservesStartupEventand calls*Manager.start();@PreDestroycallsstop()- Startup failures must be non-fatal: log an error and return; do not throw and crash the app
- Always call
lifecycleManager.removeIfExists(containerName)before creating to clean up stale containers - Use
ContainerBuilder+ContainerLifecycleManager. Do not calldockerClientdirectly in new code - Health-wait after start: TCP poll for AMQP/port-based services; HTTP poll for services with a
/readyendpoint - Config files generated for sidecars (e.g.
broker.xml) must useXmlBuilderfor XML, not string concatenation
Build & Run
./mvnw quarkus:dev
./mvnw test
./mvnw clean package
./mvnw clean package -DskipTestsFocused tests
./mvnw test -Dtest=HealthTest
./mvnw test -Dtest=BlobServiceTest#testUploadBlobManual testing
make run # start emulator in background (for manual SDK testing)
make test # run unit tests + all compat tests (starts its own Docker emulator)
make stopCompatibility Test Suite
compatibility-tests/
sdk-test-python/ : Azure SDK for Python (pytest): blob/queue/table, cosmos, vm, redis, acr,
managed identity, plus appconfig/ and keyvault/ sub-suites
sdk-test-java/ : Azure SDK for Java (BOM; the primary compat target): blob/queue/table,
cosmos + all engines, functions, keyvault, appconfig, eventhub,
servicebus, eventgrid, apim, sql, postgres, vm, datalake,
managed identity
sdk-test-node/ : Azure SDK for JS (jest): blob, queue, table, cosmos, appconfig,
keyvault, eventhub, servicebus, managed identity
sdk-test-dotnet/ : Azure SDK for .NET (TUnit): blob service SAS, cosmos (+ bracket properties,
point partition, transactional batch), keyvault (+ certificates, timestamps),
servicebus (+ lock renewal, metadata, peek-lock), signalr. The only suite
covering SignalR
sdk-test-cpp/ : Azure SDK for C++ (GoogleTest via vcpkg): blob, queue. Built from source, so ~6 min cold
compat-terraform/ : hashicorp/azurerm Terraform provider (BATS)
compat-opentofu/ : OpenTofu with the azurerm provider (BATS)
compat-azcli/ : real `az` CLI against a custom registered cloud (BATS)Guidelines:
- Always validate new implementations against the relevant SDK test suite
- Prefer SDK clients over raw HTTP for all validation
- Use
make test-<service>to run a specific suite - Use
make compat-dockerto run all suites against a running container compat-terraformandcompat-opentofumust declare the sameresourceblocks inmain.tf. CI checks it with.github/ci/compat-iac-drift-check.py; add a new resource to both suites, or list it in the matching.github/ci/compat-iac-allowlist-*.txtwith the reason
Suites that do not run in CI
Seven classes in sdk-test-java skip themselves unless an environment variable enables the
thing they test, and no workflow sets any of them, so a green sdk-test-java is not
evidence for these:
| Class | Gated on | Run it locally with |
Cosmos{Mongo,Postgres,Cassandra,Gremlin,Table,NoSql}EngineCompatibilityTest | FLOCI_AZ_SERVICES_COSMOS_ENGINES_<ENGINE>_ENABLED | make test-cosmos-<engine>, or make test-cosmos-all |
SqlCompatibilityTest | FLOCI_AZ_SERVICES_SQL_ACCEPT_EULA and FLOCI_AZ_SERVICES_SQL_DATA_PLANE_PROVIDER=managed | make test-sql |
Four of the six Cosmos engines (mongo, postgres, cassandra, gremlin) need a container
each, which is why the set is opt-in rather than part of the matrix; table and nosql are
embedded and run in-process, so they need no Docker and are cheap to run locally. The
consequence either way is that a regression in a Cosmos engine or in the SQL data plane merges
green. When you change either, run the suite locally and say so in the PR.
A suite reporting Tests run: 0 is a self-skip, never a pass. CI and the Makefile's Docker suite
targets enforce this with .github/ci/compat-test-count-check.py, which fails a suite whose JUnit
reports show no executed test (CI also applies a per-suite floor, min_tests in the matrix).
Keeping the Makefile and CI in sync
The Makefile and the CI matrix (.github/workflows/compatibility.yml) both run the SDK test containers against floci-az and must pass the same environment variables per suite.
Rule: a suite's env vars must be identical across every docker run for that suite in the Makefile and the corresponding matrix entry in .github/workflows/compatibility.yml, on both the suite-container axis (SUITE_ENV_* ↔ extra_env) and the emulator-container axis (the 6th COMPAT_SESSION argument ↔ emulator_env). Define them once (a shared variable), never inline per call site.
Suite-internal defaults (FLOCI_AZ_ENDPOINT, EVENTHUB_*, JEST_JUNIT_*) are baked as ENV in each suite's Dockerfile with the compat-network values. They do NOT belong in the Makefile or extra_env. Only network-topology overrides (sidecar hostnames/ports, emulator-endpoint redirects) go there.
There are two axes, and both must stay in sync. The suite container's env comes from
SUITE_ENV_<SUITE> in the Makefile and extra_env in the matrix. The emulator container's env comes
from the 6th COMPAT_SESSION argument in the Makefile and emulator_env in the matrix; it is easy to
miss, because a var set only there does not appear in any SUITE_ENV_* variable.
One exception on the emulator axis: compat-docker. It starts a single shared emulator with
JAVA_EMULATOR_ENV (so including FLOCI_AZ_SERVICES_SERVICE_BUS_LOCK_DURATION_SECONDS=5 and
FLOCI_AZ_SERVICES_AKS_MOCKED=true) and runs every suite against it. The python, node and dotnet suites therefore see a longer lock duration there
than under their own test-*-compat targets or in CI, where they get SERVICEBUS_EMULATOR_ENV alone.
The table below is the per-suite contract that CI and the individual targets honour; compat-docker is
a convenience runner that deliberately shares one emulator, so a Service Bus timing behaviour that only
reproduces under it is worth suspecting there first.
Suite container (SUITE_ENV_* ↔ extra_env):
| Suite | Makefile | CI (compatibility.yml extra_env) |
sdk-test-java | --add-host devstoreaccount1.dfs.core.windows.net:127.0.0.1 | ✓ |
sdk-test-java | -e FLOCI_AZ_ABFS_LOOPBACK=true | ✓ |
sdk-test-java | -e SERVICEBUS_HOST=floci-az-servicebus-default | ✓ |
sdk-test-java | -e SERVICEBUS_AMQPS_PORT=5671 | ✓ |
sdk-test-java | -e SERVICEBUS_NAMESPACE=default | ✓ |
sdk-test-java | -e AZURE_POD_IDENTITY_AUTHORITY_HOST=http://floci-az:4577 | ✓ |
sdk-test-dotnet | -e SERVICEBUS_HOST=floci-az-servicebus-default | ✓ |
sdk-test-dotnet | -e SERVICEBUS_AMQP_PORT=5672 | ✓ |
sdk-test-python | -e AZURE_POD_IDENTITY_AUTHORITY_HOST=http://floci-az:4577 | ✓ |
sdk-test-python | -e SERVICEBUS_HOST=floci-az-servicebus-default | ✓ |
sdk-test-python | -e SERVICEBUS_AMQP_PORT=5672 | ✓ |
sdk-test-python | -e SERVICEBUS_NAMESPACE=default | ✓ |
sdk-test-node | -e AZURE_POD_IDENTITY_AUTHORITY_HOST=http://floci-az:4577 | ✓ |
sdk-test-node | -e SERVICEBUS_HOST=floci-az-servicebus-default | ✓ |
sdk-test-node | -e SERVICEBUS_AMQP_PORT=5672 | ✓ |
sdk-test-node | -e SERVICEBUS_NAMESPACE=default | ✓ |
sdk-test-cpp | none, only the shared FLOCI_AZ_ENDPOINT | ✓ (no extra_env) |
Emulator container (6th COMPAT_SESSION argument ↔ emulator_env):
| Suite | Makefile | CI (compatibility.yml emulator_env) |
sdk-test-java | -e FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED=false (JAVA_EMULATOR_ENV) | ✓ |
sdk-test-java | -e FLOCI_AZ_SERVICES_SERVICE_BUS_LOCK_DURATION_SECONDS=5 | ✓ |
sdk-test-java | -e FLOCI_AZ_SERVICES_AKS_MOCKED=true | ✓ |
sdk-test-dotnet | -e FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED=false (SERVICEBUS_EMULATOR_ENV) | ✓ |
sdk-test-python | -e FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED=false (SERVICEBUS_EMULATOR_ENV) | ✓ |
sdk-test-node | -e FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED=false (SERVICEBUS_EMULATOR_ENV) | ✓ |
| every suite, parity legs only | -e FLOCI_AZ_PARITY=azure (PARITY_EMULATOR_ENV, from PARITY=azure) | ✓ (matrix.parity, scheduled and manual runs only) |
The parity legs run every suite against an emulator started at floci-az.parity=azure. In CI
they are the parity matrix axis, which is empty on pull requests and azure on the scheduled and
manually dispatched runs, so an opt-in behaviour cannot fail a PR. Locally, add PARITY=azure to any
Docker compat target (make test-terraform-compat PARITY=azure, make test-azcli PARITY=azure).
The container name follows the pattern floci-az-<service>-<namespace> (e.g. floci-az-servicebus-default). If a new sidecar-based service is added, its container name and port must be added to both places.
Testing Rules
Conventions
- Unit/integration tests:
src/test/java/io/floci/az/ - Compatibility tests:
compatibility-tests/sdk-test-*/ - Unit tests use
@QuarkusTest+ RestAssured - Sidecar-based services are disabled in unit tests via graceful Docker failure handling (no Docker socket in CI)
When touching protocol behavior
If a change affects request parsing, response shape, error handling, persistence semantics, URL generation, or service enablement:
- Add or update automated tests
- Prefer SDK-based verification in
compatibility-tests/ - Check behavior across all relevant SDK languages
- Document intentional deviations clearly
Error Handling
- Handlers return
Responsewith Azure-compatible error JSON:{"error":{"code":"...","message":"..."}} - Use
AzureErrorResponsehelper for XML-format errors (Storage services) - Sidecar startup failures must degrade gracefully: log
ERRORand skip, never throw fromonStart
Service Implementation Pattern
When adding a new HTTP-based service:
- Create
services/<svc>/package - Add
*Handler.javaimplementingAzureServiceHandler - Declare the service's routes on the handler: return a
ServiceRoutesfromroutes()naming its account suffix, host suffixes and ARM providers, and add the same literals to the goldenRoutingTableAssemblyTest. A service routed by any of those three needs noAzureRoutingFilteredit at all; the filter builds its tables fromroutes(). A service whose URLs fit none of the three (IMDS, Entra, Graph, the Cosmos root) needs a stage: a method added to thestageslist inAzureRoutingFilterand ordered against its neighbours, plus its service type inLITERAL_ROUTE_SERVICE_TYPES. That set is a declaration for the routing-drift test, not a dispatch mechanism: adding to it alone routes nothing - A service that serves ARM resource types declares them in a
*ArmSurfacebean next to its handler (ArmSurfaceProvider: namespace, lane, and each type's scope and location policy), adds the namespace toNAMESPACESintools/arm/gen_providers.py, runsmake arm-data-sync, and adds its rows toArmSurfaceGoldenTest.ArmSurfaceInventoryTestfails for a routed namespace with no surface, and theArmTypeRegistryrefuses a type that is not inproviders.json - Implement
enabled(String serviceType)on the handler, readingconfig.services().<svc>().enabled();AzureServiceRegistry.isEnabled()delegates to it, so there is no central switch to edit - Add config interface to
EmulatorConfig.ServicesConfig - Add YAML block to
application.yml - Add storage wiring if needed
- Update
BannerLogger - Add compatibility tests
- Update docs (
docs/services/<svc>.md,docs/services/index.md,mkdocs.yml,README.md)
When adding a sidecar-based service, additionally:
- Implement
*ContainerManager,*Manager,*ConfigGenerator - Expose sidecar ports in the Docker Compose example
Code Style
Dependency injection
- Always prefer constructor injection (
@Injecton the constructor) over field injection (@Injecton fields) in new or modified code:- dependencies become
private final, so the object is immutable and never observed half-constructed - required collaborators are explicit in the signature, and plain unit tests can construct the class without a CDI container
- with Quarkus Arc, normal-scoped beans are client proxies, so reading fields through an injected bean hits the proxy's own field copies, not the contextual instance. Always go through methods on injected beans; constructor injection plus method access avoids this trap entirely
- dependencies become
- When touching a class that still uses field injection, migrate it to constructor injection if the change is small; otherwise stay consistent within the class and flag it
General practices
- Make fields
finalwherever possible; avoid static mutable state - Never leave a
catchblock empty. If an exception is intentionally tolerated, log it with enough context to diagnose later. When swallowing really is correct and logging would be noise, name the variableignoredorexpectedand say in a comment why it is safe. A barecatch (Exception e) {}is never acceptable - Validate inputs at the protocol boundary and fail fast with the correct Azure error shape
- Keep methods small and focused; extract helpers instead of growing switch/if ladders
- Prefer self-explanatory code over comments; only add a comment when the WHY is non-obvious
- Always use braces in conditionals
- Follow existing project patterns before introducing new abstractions
- Use modern Java features when they improve clarity
- Use Java records for simple data carriers; model Azure resources as typed domain objects when practical
Types and names
- Do not use
var. Write the explicit type. floci-az reproduces Azure wire contracts, so the concrete type at a call site is usually the thing under review: whether a value is aLinkedHashMapor aMap, an Azure model type or a JDK one, is exactly what a reviewer needs to see. This covers local declarations, enhanced-for, classic for-init, and try-with-resources. The one exception is a record deconstruction pattern (case Node(var left, var right) ->), where naming the component types is pure noise - Import the classes you use. Do not write fully-qualified names inline.
new ArrayList<>(), nevernew java.util.ArrayList<>(). The only reason to qualify inline is a genuine name collision inside one file: import the type used more often, qualify the other, and leave a short comment naming the clash. A qualified name inside a string literal is data, not a type reference: Jolokia and JMX operation signatures such as"createQueue(java.lang.String)"must stay fully qualified, or the broker matches no operation
Imports
- No wildcard imports in
src/main, including static ones. Static wildcards stay fine in tests, whereAssertions.*,Matchers.*andRestAssured.*are the established idiom - Import order: non-
java/javaximports alphabetically, thenjava.*andjavax.*last. This is the IntelliJ default layout and what most of the tree already uses
Conventions the codebase already follows
Written down so they stay true. New code should match them without thinking. A handful of files predate them; a violation you find in the tree is a straggler, not a precedent.
- 4-space indentation, K&R braces. Never indent with a tab
- JBoss Logging, in a field named
LOG, using the parameterised...v()or...f()form. No string concatenation in log calls.src/artemis-*/is the exception and is covered under Logging below - No
printStackTrace, anywhere. NoSystem.outorSystem.errinsrc/main java.timefor everything floci-az owns.CalendarandSimpleDateFormatappear nowhere and must not be introduced. ADatesurvives only at a third-party boundary that forces one, currently the BouncyCastle certificate builders and Proton's AMQP timestamps. Convert at that boundary withDate.from(instant)and keepjava.timeon floci-az's side of it- Constructor injection in
src/main, per the section above. Field injection is fine in tests Optionalas a return type, and never as a field: there are none, keep it that way- Switch expressions over switch statements. Pattern-matching
instanceofover cast-after-check - Azure-shaped error envelopes for domain errors:
AzureErrorResponsefor the Storage XML shape, and each service's own helper for its JSON envelope (kvError,AcrErrors, CosmoserrorResponse). There is no single exception type; the envelope belongs to the service's wire contract finalon service fields, but not on locals or parameters
Tests
These describe src/test. compatibility-tests is a separate module; follow the module you are in.
- Name test methods as a camelCase sentence (
patchingAnImmutableSystemPropertyIsRejected).testXnames appear in a few older tests and are not the pattern to copy - JUnit 5 assertions with Hamcrest and RestAssured matchers. AssertJ is not used in
src/test; do not introduce it @DisplayNameis used in about 40% of test classes, mostly where the sentence carries more than a method name can. Both styles are established: match the file you are in
Documentation Style
- No em-dashes anywhere, in any content. Use colons, commas, or periods.
Logging
- Use JBoss Logging (
Logger.getLogger(MyClass.class)) insrc/main/java - Code under
src/artemis-patch/,src/artemis-amqp-patch/andsrc/artemis-plugin/is compiled into the Artemis sidecar, not into the emulator JVM, so JBoss Logging is not on its classpath. Follow the tree you are editing:src/artemis-amqp-patch/uses slf4j,src/artemis-plugin/usesSystem.Logger, andsrc/artemis-patch/logs nothing today - Use
LOG.infov(...)/LOG.debugv(...)for parameterised messages - Keep logs structured; avoid noisy logs in hot paths
- Sidecar startup/stop should always log at INFO level
Pull Request Guidelines
- Keep changes focused; avoid unrelated refactors
- Preserve behavior unless the task explicitly requires change
- Update docs when functionality changes
- Explain missing tests when behavior changed but no automated coverage was added
Conventional commits:
feat:: new service or featurefix:: bug fixperf:: performance improvementdocs:: documentation onlychore:: build, CI, dependencies
Do not add Co-Authored-By trailers for AI tools in commit messages.
Releases:
- Releases are cut by dispatching the "Release Cut" workflow
(
.github/workflows/release-cut.yml) frommain. semantic-release computes the next version from Conventional Commits, updatespom.xml/CHANGELOG.md, tags, and publishes the GitHub Release; the tag push triggersrelease.yml. - The
GH_TOKENrepo secret must be a PAT (fine-grained, contents: write): tags pushed with the defaultGITHUB_TOKENdo not triggerrelease.yml. - Use the workflow's
dry-runinput to preview the next version and notes without tagging.
Azure Protocol References
When in doubt about Azure wire protocol behavior, refer to real sources:
- Azure REST API specs: the authoritative request/response/error contract
- Azure SDK source code (Java, Python, JS)
- Microsoft official emulator behavior (Azurite for Storage, the official Event Hubs emulator)
Do not invent protocol behavior. If a spec is ambiguous, test against a real Azure endpoint or the official emulator.
Do Not
- Do not introduce non-Azure endpoint shapes
- Do not bypass
StorageFactoryfor storage operations - Do not call
dockerClientdirectly in new service code. UseContainerBuilder+ContainerLifecycleManager - Do not make sidecar startup failures fatal (they would break unit tests and environments without Docker)
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

