Commit Graph

170 Commits

Author SHA1 Message Date
Lucio Lelii 4f0f52c4f1 fix: container acting as a branch marks unselected exposed outputs NOT_SELECTED
A GenericContainer whose subflow takes only one of several internal branches
left the other branches' exposed outputs unproduced. The completion path
(reconcileGenericSubflow / GenericContainerExecutor.completedResult) returned
NodeExecutionResult.completed(valueMap) with an empty notSelectedOutputs, so
ExecutionContext.completeStep marked every unproduced exposed output
UNAVAILABLE. Downstream that reads as an error signal, not a skip: a
BranchRejoin (which self-skips only when ALL inputs are NOT_SELECTED) instead
saw UNAVAILABLE, went READY and threw BRANCH_REJOIN_INPUT_UNAVAILABLE, failing
the whole execution. A plain routing block never hits this because it reports
its not-taken outputs via notSelectedOutputs.

Both container completion paths now return NodeExecutionResult.routed(values,
allExposedOutputNames): a successful subflow that didn't take an internal
branch surfaces that branch's exposed output as NOT_SELECTED, so downstream
steps and BranchRejoins skip cleanly - letting a container act as a real
branching node (early-stop outcomes exposed as outputs routed to top-level
EndBlocks). Containers with a single always-produced output are unaffected
(that output is in the value map, notSelected is empty).

Found while building the containerized Jensen recruitment flow, where phase
containers expose early-stop outputs; added a focused regression test
(genericContainerUnselectedExposedOutputsSkipDownstreamCleanlyInsteadOfErroring)
and the "Jensen Recruitment Process - Full Revised (Containerized)" seed flow
that groups the 12 swimlanes into 7 phase containers (13 top-level nodes
instead of 39).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 21:41:52 +02:00
Lucio Lelii 1c94cd48d6 feat: harden DelimitedParserBlock (JSON fix, error codes, array output, tests)
Executed the full improvement plan for this block, found genuinely
under-baked (zero real usage, schema-only test coverage):

- Fixed a real bug: a JSON-typed output returned the raw trimmed string,
  but Input.validateValue() requires JSON-typed values to be instanceof
  JsonNode - configuring a JSON output was silently broken end to end.
  coerce() now parses the segment with ObjectMapper.readTree(), with a
  clear error on invalid JSON instead of a confusing downstream rejection.
- Replaced raw IllegalArgumentException with NodeExecutionException and
  stable error codes (DELIMITED_PARSER_INPUT_MISSING,
  _SEGMENT_COUNT_MISMATCH, _INVALID_BOOLEAN, _INVALID_FILE,
  _INVALID_JSON), matching the convention already used by
  BranchRejoinExecutor.
- Added an array-output mode: a DelimitedParserOutput (new record, kept
  separate from the shared SwitchCase used by SwitchBlock) marked
  multiple=true collects every split segment into a list with no
  fixed-count check, instead of requiring exactly N named outputs -
  complements the Iterator's "array in" with "array out" here.
- Boolean coercion now also accepts yes/no and 1/0, not just true/false.
- Added DelimitedParserExecutorTest (11 cases, was zero), two end-to-end
  ExecutionTest cases (fixed mode and array mode), and a bundled "test
  delimited parser" example flow (LLM -> DelimitedParserBlock -> EndBlock)
  since the block had never been used in any real flow before. Verified
  live against the running service with a real Ollama call.

No rename needed here - "DelimitedParserBlock" already describes what it
does, unlike ExclusiveMergeBlock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 20:41:03 +02:00
Lucio Lelii 1fff0d3dc8 refactor: rename ExclusiveMergeBlock to BranchRejoinBlock
"Merge" was misleading: the block never combines values, it only lets
exactly one of several mutually-exclusive branches (an ExclusiveOR
reconvergence, e.g. after a HumanDecisionBlock split) pass through as the
single downstream value; two arriving values is an error, not something to
combine. BranchRejoinBlock names what it actually does. There is still no
block in the platform that genuinely merges/combines multiple parallel
values into one - this rename doesn't add one, it just stops "merge" from
implying it exists.

Renamed throughout: block/config/factory/executor/activation-policy classes
and their tests, the NodeVisualRole.MERGE -> BRANCH_REJOIN visual role and
NodeTypeCapabilities.merge() -> branchRejoin() factory method, the six
EXCLUSIVE_MERGE_* validation error codes -> BRANCH_REJOIN_*, the bundled
flows.json (11 uses across the Jensen flows), and the two docs that
reference it.

Added a legacy-id fallback in DynamicBlockConfigurationTypeResolver
(type id "ExclusiveMergeBlockConfiguration") and BlockTypes (typeName
"ExclusiveMergeBlock"), mirroring the existing ChatHumanInteraction ->
ChatInteraction precedent - found necessary the hard way: without it, any
flow already persisted under the old type id fails to deserialize and
brings the whole application down at startup, not just that one flow.
Locked this in with LegacyBlockTypeIdTest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 14:17:28 +02:00
Lucio Lelii 98cf28bd9f fix: use a global input for the shared CVs array instead of duplicating it
The CVs array needs to be read by two nodes (rank-cvs and review-ranking)
that aren't wired to each other for it. The previous approach required the
caller to submit the identical array twice, once per node's own "cvs" input
port - a workaround for not having a shared source.

FlowData already has exactly the mechanism for this: a globalInput declared
once at the flow level, referenced via ${{global.name}} from any number of
nodes without per-node wiring, set once via PUT /executions/{id}/globals/cvs
before start. Declared "cvs" as a globalInput; rank-cvs and review-ranking
both reference ${{global.cvs}} and no longer declare a "cvs" Input port at
all.

Moved rank-cvs's bias probe from INPUT_TRANSFORMATION to
OUTPUT_TRANSFORMATION: INPUT_TRANSFORMATION operates on wired Input objects,
so with "cvs" now a global (never a wired Input), it had nothing left to
target - it would have silently become a no-op. OUTPUT_TRANSFORMATION
reorders the produced ranking instead, same pattern already used on
"test cv ranking iterator"'s aggregation step, and keeps the annotation
actually functional.

Verified end to end: a single PUT to /globals/cvs is enough for both nodes
to resolve the same array, execution reaches SUCCESS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:23:25 +02:00
Lucio Lelii ca349209b6 feat: rank-cvs takes an array of CVs as a single initial input
Removes the three fixed collect-cv-1/2/3 HumanInteractionBlock nodes from
"test cv ranking": rank-cvs now declares one input, cvs, via ${{cvs[]}},
fed directly as an array before start (PUT .../input/cvs/texts) instead of
requiring a human to paste each CV into its own node. Scales to any number
of CVs instead of a fixed three.

review-ranking keeps seeing both the ranking and the original CVs (also via
${{cvs[]}} in the question) for the automation-bias check to stay
meaningful - since there's no longer a node whose output naturally fans out
to both consumers, the caller submits the same array to both rank-cvs.cvs
and review-ranking.cvs.

The order-bias probe on rank-cvs now targets the whole "cvs" array: since
INPUT_TRANSFORMATION already recurses per-element for list-valued inputs,
the instruction is applied uniformly to every CV rather than to one
specific candidate as before - a different (still meaningful) experiment,
not a broken one, but worth noting as a real tradeoff of moving from named
per-candidate inputs to a single array input.

Verified end to end against the running service: no human interaction is
needed to provide the CVs, ranking is produced correctly, and review-ranking
resolves both the ranking and the full CVs array.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 12:47:13 +02:00
Lucio Lelii f28fe0312e feat: add endpoint to list all iterations of a container step
A Loop/Iterator container step only ever exposes activeInnerExecutionId (the
current iteration's child), overwritten on every iteration. Past iterations'
child executions are never deleted from the DB, but there was no way to
enumerate them - only individually fetchable by id if a caller already had
it (e.g. from the event log).

Adds GET /executions/{id}/node/{stepId}/iterations, returning every child
execution created for that container step (one per iteration for
Loop/Iterator, one per run for GenericContainer), ordered by iteration
index. Backed by a new repository query
(findByParentExecutionIdAndParentStepIdOrderByParentIterationIndexAscCreationTimeAsc)
and ExecutionsService.getContainerIterationsByOwner(), which validates the
step exists and is actually a container before querying.

Verified end to end against the running service on the "test cv ranking
iterator" flow: the endpoint lists all 3 iterations in order with distinct
ids and SUCCESS status, returns 400 for a non-container step and 404 for an
unknown step id, and each listed iteration remains individually fetchable
via the existing GET /executions/{id}.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 10:58:22 +02:00
Lucio Lelii c6efa32c79 feat: array-input marker (${{name[]}}) for LLM/HumanInteraction/HumanDecision
Placeholders written as ${{name[]}} now derive a multiple:true (array) input
instead of the default single value, on the three block types whose inputs
are parsed from free text: LLMBlockConfiguration.prompt,
HumanInteractiveBlockConfiguration.actionDescription and
HumanDecisionBlockConfiguration.question. No marker -> unchanged single-value
behavior, so every existing flow keeps working as-is.

TemplateInputs now returns a Placeholder(name, multiple) record instead of a
bare name: the "[]" suffix is stripped from the captured group and the
remaining name goes through the same name-validation added in the previous
step. HumanDecisionBlockFactory's mandatory forward-through "input" port is
untouched - only the additive context inputs parsed from the question can be
arrays.

ExecutionTemplateResolver.resolve() now also emits the "${{name[]}}" spelling
as a substitution key alongside "${{name}}" for every value, so a template
written with the marker actually gets the value substituted at render time -
without this, the literal marker was left unresolved in the text.

Added hasConsistentPlaceholderMultiplicity() alongside the existing
hasValidPlaceholderNames(): a name referenced both with and without "[]" in
the same text is now a validation error instead of silently picking one side.

Rebuilt the "test cv ranking iterator" flow (IteratorContainer scoring each
CV independently, then an LLM/HumanDecision consuming the accumulated array
via ${{scores[]}}) using the marker instead of the abandoned explicit-field
design, and verified it end to end against the running service: all three
iterations complete, the aggregation LLM receives the array and produces a
real ranking, and the review step shows both the ranking and the full array
of per-candidate scores.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 10:11:35 +02:00
Lucio Lelii 6ed8fc748f feat: validate placeholder-derived input names on LLM/HumanInteraction/HumanDecision
Nothing today rejects a ${{...}} placeholder whose captured name contains
spaces, symbols, or brackets: the capture group is `.*?`, so it accepts
anything between ${{ and }}. That name flows straight into an IODescriptor
with no further checks.

Adds an @AssertTrue check (same convention as the existing
isOptionsUnique()/areSkillIdsUnique() checks) on LLMBlockConfiguration.prompt,
HumanInteractiveBlockConfiguration.actionDescription and
HumanDecisionBlockConfiguration.question, requiring every placeholder name to
start with a letter and contain only letters, digits, '-', '_' or '.'.

'.' stays allowed because it's already load-bearing: LoopContainer guard
prompts reference inputs.<name>/outputs.<name>
(ExecutionsService.buildGuardTemplateValues), and colliding container-exposed
names get qualified as nodeName.ioName
(ContainerFlowInterfaceResolver.qualifyWithNodeName). Confirmed via the full
suite - the first pass of this change broke 5 Loop container tests before
'.' was added back.

'[' and ']' stay rejected on purpose, reserving that syntax for a possible
future array-input marker on placeholder names.

TemplateInputs made public (was package-private) so the three
BlockConfiguration classes, which live in a different package, can call the
new hasValidPlaceholderNames() check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 09:55:24 +02:00
Lucio Lelii e11ef08537 fix: container completion no longer hangs in RUNNING on a downstream output error
Step.completeSuspendedContainer() (the async path that resumes a container
step once its inner subflow/iteration finishes) propagated container
outputs to downstream inputs without the same try/catch guard that
Step.run()'s synchronous path already had. When a downstream input rejected
the value (e.g. a multiplicity mismatch), the exception escaped through the
event-listener callback that drives it, leaving the container step stuck in
RUNNING forever with no recorded error.

Found while prototyping an IteratorContainer-based CV ranking flow: an
Iterator's accumulated (multiple) output wired into a plain LLM input
(always declared non-multiple) reproduced exactly this hang.

Wrapped the same body in try/catch, mirroring run()'s failure handling:
mark the step FAILED and notify the listener with a proper error message
instead of silently stalling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 09:05:37 +02:00
Lucio Lelii ba4f05ba0a feat: add "test cv ranking" example flow with multi-CV input and ranking output
Three CVs are collected separately, ranked together by one LLM call (three
named inputs feeding a single ranking prompt), and reviewed by a human
decision maker who sees both the ranking and all three original CVs side by
side via the newly added multi-input support on HumanDecisionBlock.

Carries starter design-time bias annotations and probes on the ranking step
(SELECTION_BIAS, INPUT_TRANSFORMATION on one CV) and the review step
(AUTOMATION_BIAS, ROUTING_OVERRIDE) as a base for later bias-injection
experiments on ranking tasks specifically. Verified end to end against the
running service: full run reaches SUCCESS with a real ranking produced by
the LLM and accepted by the reviewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:43:47 +02:00
Lucio Lelii 9f4dc5303b feat: allow HumanInteractionBlock and HumanDecisionBlock to declare multiple inputs
Both block types previously exposed exactly one fixed input port, so a human
step could only ever be wired to a single upstream value even when the
decision or task genuinely needs more context to evaluate (e.g. seeing both
the original candidate profile and an automated screening result before
deciding).

Following the same convention LLMBlock already uses for its prompt, extra
named input ports are now derived from ${{name}} placeholders in the task's
free text: actionDescription for HumanInteractionBlock, question for
HumanDecisionBlock. No placeholders -> unchanged single "input" port, so
every existing flow keeps working as-is.

HumanDecisionBlock keeps its "input" port mandatory regardless of
placeholders, since HumanDecisionExecutor forwards that exact value as the
payload of whichever branch is chosen; extra placeholder-derived inputs are
additive context alongside it. HumanInteractionBlock has no such constraint,
so its inputs are fully derived when placeholders are present.

Extracted the placeholder-parsing regex (previously private to
LLMBlockFactory) into a shared TemplateInputs helper reused by all three
factories.

Wires the new capability into the bundled "test biased" flow: shortlist-decision
now also sees the candidate profile alongside the screening assessment, and
the container's reference-check step sees both the candidate profile and the
screening assessment. Verified end to end against the running service.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:34:11 +02:00
Lucio Lelii 7f92e1a5bf test: add interactive container to the bias test flow
Adds a GenericContainer (reference-check-container) between the shortlist
decision and interview prep steps, with an interactive human step feeding an
LLM assessment that carries its own bias annotation and behavioral probe.
Exercises subflow bias propagation (includeSubflow) and interactive nodes
inside containers together on the bundled bias test flow. Verified end to
end against the running service: normal run reaches SUCCESS, and a
bias-rerun with includeSubflow on the container correctly activates and
applies the inner probe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:08:51 +02:00
Lucio Lelii a31e1692f6 feat: durable container coordinator and non-blocking Loop/Iterator subflows
Closes Tappa B and Tappa C of the interactive-containers plan.

Tappa B (recovery and lifecycle):
- Generalize the GenericContainer-only watcher into a type-dispatching
  coordinator (watchContainerSubflow/reconcileContainerSubflow) shared by
  Generic, Loop and Iterator.
- Add reconcileContainerSubflowsOnStartup (ApplicationReadyEvent): re-arms
  every persisted parent-child link at boot using only the entity's own
  parentExecutionId/parentStepId columns, so a child that already finished
  while the process was down is reconciled immediately, and a pending one
  gets its listener re-armed. No longer relies on any in-memory listener
  surviving a restart.
- cancelExecution now propagates in both directions: cancelling a parent
  cancels its active container child; cancelling a child fails the
  parent's container step (same outcome as an unexpected child error).
  removeExecution evicts cached children too.
- Propagate simulation mode to container children: Step gains
  executionSimulationEnabled (mirrors interactionSimulationDescriptor's
  propagation to every step, containers included); ContainerExecutionContext
  carries it plus the descriptor; startContainerChild starts the child via
  startSimulationExecution when applicable. ExecutionObject.hasSimulationAvailable
  now recognizes interactive nodes nested inside a container's subflow(s),
  which it previously ignored entirely since a container is never itself
  isUserInteractive().

Tappa C (Loop/Iterator non-blocking):
- LoopContainerExecutor and IteratorContainerExecutor become thin adapters
  delegating to ExecutionsService.startLoopSubflow/startIteratorSubflow.
  All advancement logic (creating/starting a child, checking whether it
  finished synchronously vs. suspending) now lives in ExecutionsService, so
  it is reusable both from the initial call and from reconciliation.
- Iterator advances iteration-by-iteration in a loop, chaining synchronous
  completions without suspending, and persists remainingValues/
  runtimeInputValues/accumulatedOutputs so it can resume exactly where it
  left off.
- Loop is a two-phase (MAIN/GUARD) state machine; the phase to resume into
  is derived from the completed child's own subflowRole, so it doesn't need
  separate persistence. Persists currentInputs/latestOutputs.
- FlowDataValidator and FlowExecutionValidator now accept interactive nodes
  in the subFlow of all three container types; LoopContainer's guardSubFlow
  remains rejected (guard interactivity is out of scope).

Existing container tests that assumed the old fully-synchronous model
(assert SUCCESS right after start()) are updated: since a child's steps
always run on their own thread pool, even a fully-automatic container now
transiently visits WAITING before the coordinator resolves it, so tests
must poll through WAITING too, not just RUNNING.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 20:28:06 +02:00
Lucio Lelii 0e5831289f feat: suspend generic containers for subflows 2026-07-23 19:25:21 +02:00
Lucio Lelii a64642923f feat: persist container subflow relationships 2026-07-23 19:11:41 +02:00
Lucio Lelii c2ae39f292 refactor: move container bias handling into subflows 2026-07-23 18:50:43 +02:00
Lucio Lelii 47dc3b3c3b feat: propagate bias context into container subflows (coarse model)
A bias variant rerun can now activate a container's inner subflow: with
includeSubflow=true on a container activation, every executable bias
annotation on the subflow nodes (and a LoopContainer's guardSubFlow) is
activated as a block, and the bias context is propagated into the
container's inner execution so those probes fire at runtime.

- BiasActivation gains includeSubflow (default false, backward compatible).
- BiasExecutionContext carries subflowActivatedContainerIds.
- BiasContainerPropagation builds the inner context by scanning the
  subflow for executable annotations.
- ContainerExecutor.execute receives the BiasExecutionContext; the three
  container executors create the inner execution with the propagated
  context (Loop applies it to subFlow and guardSubFlow, per iteration).
- validateBiasActivations validates includeSubflow (container-only, must
  have an executable probe) and no longer requires annotationIds when it
  is set; new error codes BIAS_ACTIVATION_ANNOTATIONS_REQUIRED,
  BIAS_SUBFLOW_ON_NON_CONTAINER, BIAS_SUBFLOW_NOT_EXECUTABLE.
- compareFullFlow treats subflow-activated containers as activated nodes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 18:17:37 +02:00
Lucio Lelii 1752b4ed15 feat: enable bias injection on human interaction nodes
Make a bias variant rerun affect human interaction nodes, both when
answered by a real human and when simulated:

- ExecutionStepView now exposes activeBiasProbes (annotationId,
  activationMode, instruction) for a step during a BIAS_VARIANT rerun,
  so the frontend can surface the injected bias to the interacting user.
  It is empty in normal/simulation runs, leaving the contract unchanged.
- PROMPT_DIRECTIVE is now supported on HumanInteraction/HumanDecision.
  It biases the simulator prompt and is a no-op when a real human
  answers (the directive is shown via activeBiasProbes instead).
- HumanInteractionExecutor.simulate and HumanDecisionExecutor.simulate
  now decorate their prompt via BiasRuntimeSupport.decoratePrompt, so an
  active prompt directive reaches the simulator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 16:50:16 +02:00
Lucio Lelii 10420ab6b7 refactor: drop the unused EndMode enum from EndBlockConfiguration
EndMode only ever had one value (PATH_END) and wasn't read anywhere,
so it was pure dead configuration. Keep JSON backward compatibility by
ignoring a legacy "mode" field on deserialization instead of failing,
and drop it from the seed flows and docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:55:35 +02:00
Lucio Lelii 2a349befa5 fix: let HumanDecision blocks support execution simulation
HumanDecisionExecutor explicitly disabled supportsSimulation(), so any
execution containing a HumanDecision node lost the aggregate
simulationAvailable flag entirely, hiding the simulate feature from
clients even though the /simulate endpoint itself was untouched.
simulate() now prompts the simulator LLM to pick one of the configured
options (and a rationale when required), validates the choice, and
returns the same output shape a real human interaction would produce.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:54:59 +02:00
Lucio Lelii a0ed4874d8 feat: add node capabilities and biased seed flow 2026-07-22 17:38:42 +02:00
Lucio Lelii 007bca14fb fix: add missing Flyway autoconfiguration and shorten an overlong flow description
pom.xml declared org.flywaydb:flyway-core directly, but Spring Boot 4 moved
Flyway's autoconfiguration into a separate spring-boot-flyway module: with
only flyway-core on the classpath, Flyway never ran (no migration, not even
flyway_schema_history), so ddl-auto=validate always failed against an empty
schema. Switch to org.springframework.boot:spring-boot-starter-flyway, which
pulls in the actual autoconfiguration.

The "Jensen Recruitment Process - Mitigated (Structured)" seed description
was 269 characters, over the default varchar(255) on flow_entity.description
(no @Column(length=...) override), so FlowImportComponent failed to insert
it while every other flow (all <= 255 chars) imported fine. Shortened to the
same content in 236 characters.

Verified end-to-end against a real Postgres container: fresh schema
bootstrap (ddl-auto=update once, then default validate), all 8 seed flows
import cleanly, full test suite unaffected (362 passing, 2 skipped).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 15:24:51 +02:00
Lucio Lelii 26ab50f06c fix: remove unused --enable-preview and clean up compiler warnings
No source in this codebase actually uses a preview language feature, but
--enable-preview combined with release=25 in pom.xml (compiler + surefire)
was tripping the IDE's language server on every file ("preview can be
enabled only at source level 26"), which read as pervasive warnings across
the whole project. Removed both flags; full suite still passes unchanged.

Also fixes a handful of genuine compiler warnings found via a manual
`javac -Xlint:all` pass: dead unused method in BlockCatalogService, two
uses of JsonNode.isTextual()/asText() (deprecated in tools.jackson 3) in
favor of isString()/asString(), and a Javadoc comment in
ExecutionsController placed after @GetMapping instead of before it (so it
was never attached to the method).

Left the remaining low-value warnings (missing serialVersionUID on a
handful of exception/resolver classes, this-escape in
ExecutionObject/ExecutionContext/Step) untouched per user's choice - purely
cosmetic and, for this-escape, riskier to touch without a concrete reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 14:21:09 +02:00
Lucio Lelii 28670d2436 test: close gaps against the control-flow doc's test checklist
Audits every bullet in "Strategia di test" (unit/integration/regression/
concurrency) against the actual suite and adds coverage for what was
missing: ExclusiveMergeActivationPolicy and ExclusiveMergeExecutor direct
unit tests, dynamic configuration validation, a second-hop NOT_SELECTED
propagation assertion, implicit-merge rejection (EXCLUSIVE_BRANCH_MERGE),
a lanes/laneId JSON round-trip, per-block-type bias capability checks, and
concurrency tests for the merge (repeated concurrent split+merge, cancel
under concurrency, concurrent snapshot reads).

Fixes a latent bug found while writing the configuration-validation tests:
ExclusiveMergeBlockConfiguration.areInputsUnique() and
HumanDecisionBlockConfiguration.areOptionsUnique() never fired because Bean
Validation only recognizes isXxx/getXxx as constrained getters, so the
duplicate-name check was silently unenforced everywhere, including flow
creation. Renamed to isInputsUnique()/isOptionsUnique().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 12:35:24 +02:00
Lucio Lelii 2d6f12a019 feat: add structured Jensen recruitment flows (phase 6)
Adds "Jensen Recruitment Process - Full Revised (Structured)" and
"... Mitigated (Structured)" seed flows, ported node-per-node from the
original PlantUML activity diagrams using HumanDecisionBlock gateways,
explicit ExclusiveMergeBlock rejoins, EndBlock outcomes and FlowLane
swimlanes. Preserves the 5 CONFIRMED risk annotations and all 16
FH-J1-FH-J16 MITIGATED annotations on the corresponding structured nodes.
Does not replace the existing unstructured macro-flow seeds.

Human-interactive stages are modeled as HumanInteractionBlock rather than
GenericContainer/LLMBlock so both flows can be driven to a real SUCCESS
outcome without an Ollama model. Adds structural validation tests and a
runtime test suite driving all five terminal outcomes on both flows, a
snapshot-restore check during a pending HumanDecisionBlock, and a
baseline/bias-variant comparison showing a ROUTING_OVERRIDE changing both
the branch taken and the final outcome.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 12:08:58 +02:00
Lucio Lelii 1cf780966b feat: add swimlanes and JSON dossier IO type (phases 4-5)
Implements Phase 4 (FlowLane/laneId on FlowData, Block and Container with
dedicated validation) and Phase 5 (IOType.JSON, optional value schema on
IODescriptor, JSON conversion/validation in Input, template rendering) from
the control-flow engine implementation plan. Updates the plan document's
progress table and phase sections accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 11:32:25 +02:00
Lucio Lelii f1ade0d8e9 feat: add explicit gateways and flow outcomes 2026-07-22 11:06:10 +02:00
Lucio Lelii b8fbbe8de0 feat: add control-flow runtime foundations 2026-07-22 10:49:21 +02:00
Lucio Lelii 3280584d31 feat: extend bias system and add Jensen flows 2026-07-22 10:40:44 +02:00
Lucio Lelii 587eadc678 Implement bias impact experiments 2026-07-20 17:22:26 +02:00
Lucio Lelii 366b048689 Add bias annotations to workflow blocks 2026-07-20 16:18:57 +02:00
Lucio Lelii a9ea3d5401 Merge branch 'feature/loop-container-guard-subflow' 2026-05-27 10:40:12 +02:00
Lucio Lelii 163ba9afc2 Add execution rerun history groups 2026-05-27 10:37:03 +02:00
Lucio Lelii 84ff2daa05 Add static favicon 2026-05-26 23:22:01 +02:00
Lucio Lelii 68c9bca323 Add guard subflow loop feedback mapping 2026-05-26 23:21:31 +02:00
Lucio Lelii 83c76ebd4f Add generic DelimitedParser block with configurable outputs 2026-05-26 23:13:46 +02:00
Lucio Lelii 34c1a60d33 - added a subflow as guard of the loop container
- validation of the subflow by type
- added type boolean as output of nodes
2026-05-26 10:15:39 +02:00
Lucio Lelii e12ce82406 Fix boolean LLM response parsing 2026-05-20 17:11:35 +02:00
Lucio Lelii b6a08d1b05 Validate shared MCP consumers without producers 2026-05-20 12:27:25 +02:00
Lucio Lelii 35db1d42f1 Improve assistant model routing and diagnostics 2026-05-20 12:27:09 +02:00
Lucio Lelii c6e538a80b Add dedicated WebClient pools for LLM and MCP 2026-05-20 12:26:35 +02:00
Lucio Lelii 77f5937ec4 test fix 2026-05-20 09:35:19 +02:00
Lucio Lelii 174f99918d Stabilize assistant and integration tests 2026-05-19 13:39:27 +02:00
Lucio Lelii 4bd240ddbb Improve provider failure diagnostics 2026-05-19 13:39:17 +02:00
Lucio Lelii d466459a27 Add assistant call cancellation endpoint 2026-05-19 13:39:11 +02:00
Lucio Lelii 7b92c5b6ab Default HTTP auth type when disabled 2026-05-19 13:39:07 +02:00
Lucio Lelii ff824ada44 Preserve unchanged assistant blocks 2026-05-19 13:37:48 +02:00
Lucio Lelii 8d93f2e161 test(executors): aggiunta nuova directory di test per executors 2026-05-18 16:34:15 +02:00
Lucio Lelii 63e30ab850 test(assistant): aggiunti test su draft plan minimo e cancellazione chiamata 2026-05-18 16:34:07 +02:00
Lucio Lelii c005a09b6f config: cambio modello assistant di default a qwen3:32b-q4_K_M 2026-05-18 16:34:00 +02:00