Conversation
jimador
added this pull request to stack #2067
September 27, 2026 20:48
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 27, 2026 21:14
0a0d675 to
db3d0ce
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 27, 2026 22:10
47ebedc to
14ca1cc
Compare
jimador
removed this pull request from stack #2067
September 27, 2026 22:10
jimador
added this pull request to stack #2081
September 27, 2026 22:11
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 01:38
14ca1cc to
46d1912
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 02:15
46d1912 to
b2da4c2
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
2 times, most recently
from
September 28, 2026 04:26
fbfc459 to
7598bf2
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 05:07
7598bf2 to
c6f93d5
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 05:19
c6f93d5 to
df7bb24
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 16:07
2431900 to
5a1baa9
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 16:48
5a1baa9 to
019639e
Compare
jimador
removed this pull request from stack #2081
September 28, 2026 16:48
jimador
added this pull request to stack #2087
September 28, 2026 16:49
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 28, 2026 23:47
019639e to
228756f
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 29, 2026 01:27
228756f to
6f7e6f8
Compare
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 29, 2026 21:45
461d753 to
a66f006
Compare
…otations Adds embabel-agent-decision-annotations, an optional and unpublished module that holds the question annotations for reading decision specs from Java types. An architecture test keeps main classes to the core decision types, Jackson and the JDK, and checks annotation retention and targets. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
AnnotatedDecisions reads a DecisionSpec from a record or bean under one Jackson mapper and caches each successful result per type. Question names, option ids, level ids and question order come from the mapper's creation view of the type and its serialized enum form. The parser collects every problem in the type and reports them together in one AnnotatedDecisionException, each naming the member, the cause and the fix. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
AnnotatedDecision.project(response) checks that the response answers the type's spec and converts the answers through the mapper that read the type. project(response, otherProperties) also sets the properties without question annotations and reports every missing, unknown or question-named key at once. It returns the value alone because DecisionProjection has no public way to hold values that did not come from the answers. The support-triage spec is checked in as JSON. One test compares the generated JSON with it and checks that it names no Java class. Another executes it through the stub service using only the core decision types. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…t findings The support-triage example reads one record under a plain mapper and under a Spring Boot mapper configured for snake case. The question names and spec id follow the mapper, and projection works under both. The Dice-shaped example projects a proposition review whose identity comes from otherProperties, and a policy reads the entity and the response provenance. An inconclusive answer fails projection and the policy defers. The Kotlin test checks that the DSL spec equals the Java record spec, and records annotation placement on a data class. Only @get: reads. The default site and @PARAM: also land on copy() parameters, and Jackson drops the private backing field that @field: annotates. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
… data classes A Kotlin data class repeats each constructor parameter annotation on the matching parameter of its generated copy(). The orphan scan skips a copy() parameter that carries the same question annotations as the constructor parameter, so the default site and @PARAM: read on a data class and give the same spec and definition id as the Java record. A field or method whose name matches a property that Jackson built from other members gets its own message. It names the property and the members Jackson reads for it, and adds the Kotlin use-site targets that work when the type is a Kotlin class. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Kotlin test sources compile before Java test sources, so the Kotlin test declares its own enums and loads the Java record by name. The test also records that @PARAM: and @field: together fail on the backing field, which is the placement a later Kotlin default site produces, and marks the data class fixtures for the reference docs. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…nd its evaluation The reference section covers when to use annotations, the annotations and their targets, enums for options and levels, names and order from Jackson, projection and its limits, the portable spec JSON, the error messages and the Kotlin placements. The evaluation section records ergonomics, code size, supported types, costs, the removal steps and the recommendation. The example tests carry the tagged regions the section includes. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…he constructor annotation Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
The cache stores each parsed decision with its class, so a class and its loader are collected once the application drops them. A parse failure throws out of computeValue, which stores nothing, and the type is read again on the next call. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…ethods outside a property The orphan scan walks every interface of the type and its superclasses, each once, and reports annotated members that belong to no Jackson property. An interface method counts as covered when a property method has its signature, including a getter inherited from a superclass that does not implement the interface. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…data annotation type The parser loads kotlin.Metadata once through its own class loader and checks each class with isAnnotationPresent. The type is null when Kotlin is absent, so the module keeps no Kotlin dependency. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…maller methods Property reading, question checks, enum entry reading and the orphan scan each move into their own private methods. The per-parse collections become fields of the one-shot parser. Every problem message stays the same. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…ests Each assertThrows lambda makes one throwing call, with its arguments built beforehand. Question names use a method reference, the multi-line message uses a text block, and the failed-parse check uses assertNotSame. The cache test waits for collection on a ReferenceQueue with a timeout. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…alue before projecting Responses no longer carry a spec id, so project checks the response with requireMatches. - A mismatch still throws DecisionProjectionException. The message now says what differs and keeps the hint to ask with spec(). - A response to reworded questions still projects, since instructions are not compared. A test pins this. - Tests compare specs by equality and check responses with requireMatches. - The prototype page drops the ids and updates the size figures. Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
… enum Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…ion spec regardless of other properties Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
…m its new package Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
jimador
force-pushed
the
prototype/annotated-decision-spec
branch
from
September 29, 2026 23:21
a66f006 to
1cb574f
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Prototype. This PR adds an opt-in module,
embabel-agent-decision-annotations, that reads a decision spec from an annotated record or class and projects a response back into it. It's here for evaluation. The module setsmaven.deploy.skip; keeping it off Maven Central still needs confirming (condition 1).Declaring a spec
A record with question annotations declares the same spec as the builder:
Enum constants become options or levels, and
@Describedsets their descriptions. Question names come from Jackson, so@JsonPropertyand the mapper's naming strategy apply.AnnotatedDecisions.using(mapper)reads with an application's own mapper.Projection checks the response against the type's spec with
requireMatches, then refuses an answer that is inconclusive, failed, no-match, or a rating with no selected level, asDecisionProjectiondoes.project(response)refuses a type with properties that aren't questions.project(response, otherProperties)fills those, such as an id, and returnsT.Classification
An enum annotated with
@Classificationreads as aCategoryMapping:Category ids are the constant names. A constant without
@Describedis an error, as it is for a choice option. A decision type whose only question is a choice reads as aClassificationSpec, even with other properties such as an id, so its spec also goes toclassify.Errors
The parser reports every problem on a type in one
AnnotatedDecisionException, each with its member, or the type when the problem is the whole type, and the fix. For example, a Kotlin@field:annotation on a data class:The reference page has a table of the parser's messages and their fixes.
Kotlin
The Kotlin tests read through
using(mapper)with jackson-module-kotlin 3.1 on Kotlin 2.2. No test coversdefaults()with a Kotlin class.@param:@get:@param:and@field:together (the placement a later Kotlin default site produces)@field:alone@property:Recommendation
Keep it as an opt-in prototype, unpublished until this recommendation is accepted. It produces specs equal to the builder's, projects into the declaring type after checking the response against its spec, and names the member and fix for every problem. Drop it if no consumer application adopts it by the release that promotes the decision types.
Conditions for accepting it:
maven.deploy.skip, but the Central plugin lives in the build parent.DecisionProjectionsoproject(response, otherProperties)can returnDecisionProjection<T>instead ofT.Values in
otherPropertiesare coerced leniently by Jackson:"3"and3.0fill anint, and"true"fills aboolean. No test pins this. A stricter rule would need its own decision.The parser's cache doesn't hold classes alive. Jackson's own caches in the
defaults()mapper can still hold a projected type, so an application that reloads classes should pass its own mapper withusing(mapper).Size and removal
DecisionTypeParserNo other module refers to it. Removal is the module directory, its
<module>line, the reference page and itsinclude::line.Stacked on #2076.