Component
Core Engine
Description
Actual behavior:
Capability creates a single shared OperationStepExecutor instance for all aggregate
functions across all aggregates:
OperationStepExecutor sharedExecutor = new OperationStepExecutor(this);
for (AggregateSpec aggSpec : spec.getCapability().getAggregates()) {
this.aggregates.add(new Aggregate(aggSpec, sharedExecutor));
}
AggregateFunction.execute() calls stepExecutor.setExposeNamespace(namespace) immediately
before executeSteps(). Because the executor is shared, two concurrent requests targeting
functions from aggregates with different namespaces (e.g. shipyard and logistics) can
interleave:
- Thread A:
setExposeNamespace("shipyard")
- Thread B:
setExposeNamespace("logistics")
- Thread A:
executeSteps(...) reads exposeNamespace → gets "logistics" → wrong
namespace-qualified references resolved
The volatile keyword guarantees visibility but not atomicity of the set+use pair.
Expected behavior:
Each AggregateFunction execution should resolve namespace-qualified references using its
own aggregate's namespace, regardless of concurrent calls to other functions.
Root Cause
Capability shares one OperationStepExecutor across all AggregateFunction instances.
exposeNamespace is a mutable volatile field set just before each execution, making the
set+use sequence non-atomic and subject to a TOCTOU race under concurrency.
By contrast, ToolHandler, ResourceHandler, and ResourceRestlet each own a dedicated
executor with exposeNamespace set once at construction time — they are not affected.
Suggested Fix
Give each AggregateFunction its own OperationStepExecutor instance (constructed in
Aggregate rather than in Capability), so exposeNamespace can be set immutably at
construction time. This aligns the aggregate execution model with the handler execution model.
Steps to Reproduce
- Define a capability with two aggregates of different namespaces, each with an orchestrated function that has a step-level
with using namespace-qualified references
- Send concurrent requests to both functions (e.g. via two simultaneous MCP tool calls)
- Observe that one or both calls resolve
with values against the wrong namespace, producing
incorrect HTTP request parameters
Capability File (if relevant)
naftiko: "1.0.0-alpha2"
info:
label: "Shared Executor Namespace Race Condition"
description: "Reproduces concurrent namespace pollution on the shared OperationStepExecutor"
tags:
- Test
- Aggregate
- Concurrency
created: "2026-04-14"
modified: "2026-04-14"
capability:
aggregates:
- label: "Shipyard"
namespace: "shipyard"
functions:
- name: "get-voyage-manifest"
description: "Assemble a voyage manifest."
inputParameters:
- name: "voyage-id"
type: "string"
description: "Voyage identifier"
steps:
- name: "fetch-voyage"
type: call
call: "registry.get-voyage"
with:
voyage-id: "shipyard.voyage-id" # must resolve to caller's voyage-id
mappings:
- targetName: "voyage-id"
value: "$.fetch-voyage.voyageId"
outputParameters:
- name: "voyage-id"
type: "string"
- label: "Logistics"
namespace: "logistics"
functions:
- name: "get-shipment-status"
description: "Retrieve the status of a shipment."
inputParameters:
- name: "shipment-id"
type: "string"
description: "Shipment identifier"
steps:
- name: "fetch-shipment"
type: call
call: "warehouse.get-shipment"
with:
shipment-id: "logistics.shipment-id" # must resolve to caller's shipment-id
mappings:
- targetName: "shipment-id"
value: "$.fetch-shipment.shipmentId"
outputParameters:
- name: "shipment-id"
type: "string"
exposes:
- type: "mcp"
address: "localhost"
port: 9300
namespace: "fleet-mcp"
description: "MCP server exposing both aggregate functions."
tools:
- name: "get-voyage-manifest"
description: "Get the voyage manifest."
ref: "shipyard.get-voyage-manifest"
- name: "get-shipment-status"
description: "Get the shipment status."
ref: "logistics.get-shipment-status"
consumes:
- type: "http"
namespace: "registry"
baseUri: "http://localhost:19999"
resources:
- name: "voyage"
path: "/voyages/{{voyage-id}}"
operations:
- name: "get-voyage"
method: GET
inputParameters:
- name: "voyage-id"
in: path
- type: "http"
namespace: "warehouse"
baseUri: "http://localhost:19998"
resources:
- name: "shipment"
path: "/shipments/{{shipment-id}}"
operations:
- name: "get-shipment"
method: GET
inputParameters:
- name: "shipment-id"
in: path
Logs & Stacktrace
Version
1.0.0-alpha2
Runtime
JVM
Agent Context (optional)
agent_name: GitHub Copilot
llm: Claude Sonnet 4.6
tool: VS Code Chat
confidence: high
source_event: pr_review_317
discovery_method: code_review
files_suspected:
- src/main/java/io/naftiko/Capability.java
- src/main/java/io/naftiko/engine/aggregates/Aggregate.java
- src/main/java/io/naftiko/engine/aggregates/AggregateFunction.java
- src/main/java/io/naftiko/engine/exposes/OperationStepExecutor.java
Component
Core Engine
Description
Actual behavior:
Capabilitycreates a single sharedOperationStepExecutorinstance for all aggregatefunctions across all aggregates:
AggregateFunction.execute()callsstepExecutor.setExposeNamespace(namespace)immediatelybefore
executeSteps(). Because the executor is shared, two concurrent requests targetingfunctions from aggregates with different namespaces (e.g.
shipyardandlogistics) caninterleave:
setExposeNamespace("shipyard")setExposeNamespace("logistics")executeSteps(...)readsexposeNamespace→ gets"logistics"→ wrongnamespace-qualified references resolved
The
volatilekeyword guarantees visibility but not atomicity of the set+use pair.Expected behavior:
Each
AggregateFunctionexecution should resolve namespace-qualified references using itsown aggregate's namespace, regardless of concurrent calls to other functions.
Root Cause
Capabilityshares oneOperationStepExecutoracross allAggregateFunctioninstances.exposeNamespaceis a mutablevolatilefield set just before each execution, making theset+use sequence non-atomic and subject to a TOCTOU race under concurrency.
By contrast,
ToolHandler,ResourceHandler, andResourceRestleteach own a dedicatedexecutor with
exposeNamespaceset once at construction time — they are not affected.Suggested Fix
Give each
AggregateFunctionits ownOperationStepExecutorinstance (constructed inAggregaterather than inCapability), soexposeNamespacecan be set immutably atconstruction time. This aligns the aggregate execution model with the handler execution model.
Steps to Reproduce
withusing namespace-qualified referenceswithvalues against the wrong namespace, producingincorrect HTTP request parameters
Capability File (if relevant)
Logs & Stacktrace
Version
1.0.0-alpha2
Runtime
JVM
Agent Context (optional)