Skip to content

Commit 1ad18ba

Browse files
dmealingclaude
andcommitted
feat(render): FR-010 verify output-prompt coverage + render->recover round-trip
Add Verify.checkOutputPrompt(fragment, requiredFieldNames) with the new ERR_OUTPUT_PROMPT_FIELD_MISSING error code. Performs a substring scan of the rendered output-format fragment to confirm every required field name appears in the emitted skeleton — the build-time half of the prompt-fragment contract. Add OutputPromptVerifyTest covering: coverage flags a missing field, coverage passes when all fields present, JSON exampleOnly round-trip (render→recover) produces no lostRequired, XML exampleOnly round-trip produces no lostRequired. All four tests green; full render suite 211/211. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 52ab3ab commit 1ad18ba

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

server/java/render/src/main/java/com/metaobjects/render/Verify.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,38 @@ public final class Verify {
3333
public static final String ERR_REQUIRED_SLOT_UNUSED = "ERR_REQUIRED_SLOT_UNUSED";
3434
/** A declared {@code @requiredTags} output tag is absent from the template text. */
3535
public static final String ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING";
36+
/** A required field name is absent from the rendered output-format fragment. */
37+
public static final String ERR_OUTPUT_PROMPT_FIELD_MISSING = "ERR_OUTPUT_PROMPT_FIELD_MISSING";
3638

3739
static final int MAX_DEPTH = 32;
3840

3941
private Verify() {}
4042

43+
// ---------------- Public API (output-prompt coverage) ----------------
44+
45+
/**
46+
* Check that every required field name appears (as a substring) in the rendered
47+
* output-format fragment. The check is a simple substring scan — field names are
48+
* plain identifiers, so occurrence anywhere in the fragment is sufficient evidence
49+
* that the renderer included the field.
50+
*
51+
* @param fragment the rendered output-format fragment (XML or JSON skeleton)
52+
* @param requiredFieldNames the field names that must appear in the fragment
53+
* @return a list of {@link VerifyError} with code {@link #ERR_OUTPUT_PROMPT_FIELD_MISSING}
54+
* for every field name absent from the fragment; empty when all are present
55+
*/
56+
public static List<VerifyError> checkOutputPrompt(String fragment, List<String> requiredFieldNames) {
57+
List<VerifyError> errors = new ArrayList<>();
58+
String haystack = fragment == null ? "" : fragment;
59+
if (requiredFieldNames == null) return errors;
60+
for (String name : requiredFieldNames) {
61+
if (!haystack.contains(name)) {
62+
errors.add(new VerifyError(ERR_OUTPUT_PROMPT_FIELD_MISSING, name));
63+
}
64+
}
65+
return errors;
66+
}
67+
4168
// ---------------- Mustache tag token model (rendering-agnostic) ----------------
4269

4370
sealed interface Tok permits VarTok, SectionTok, PartialTok {}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.metaobjects.render;
2+
3+
import com.metaobjects.render.prompt.*;
4+
import com.metaobjects.render.recover.*;
5+
import org.junit.Test;
6+
import java.util.List;
7+
import java.util.Map;
8+
import static org.junit.Assert.*;
9+
10+
public class OutputPromptVerifyTest {
11+
12+
@Test public void coverageFlagsMissingRequiredField() {
13+
List<VerifyError> e = Verify.checkOutputPrompt("<Answer><text>hi</text></Answer>",
14+
List.of("text", "confidence"));
15+
assertEquals(1, e.size());
16+
assertEquals(Verify.ERR_OUTPUT_PROMPT_FIELD_MISSING, e.get(0).code());
17+
assertEquals("confidence", e.get(0).path());
18+
}
19+
20+
@Test public void coverageAllPresentNoFindings() {
21+
List<VerifyError> e = Verify.checkOutputPrompt("<Answer><text>hi</text><confidence>HIGH</confidence></Answer>",
22+
List.of("text", "confidence"));
23+
assertTrue(e.isEmpty());
24+
}
25+
26+
@Test public void roundTripExampleOnlyRecoversComplete() {
27+
// render the exampleOnly skeleton, then recover() it — required fields must all be present
28+
OutputFormatSpec spec = new OutputFormatSpec(Format.JSON, "Answer", PromptStyle.EXAMPLE_ONLY, List.of(
29+
new PromptField("text", FieldKind.STRING, true, false, null, null, "hi", null, null),
30+
new PromptField("confidence", FieldKind.ENUM, true, false, List.of("HIGH"), null, "HIGH", null, null)));
31+
String fragment = OutputFormatRenderer.render(spec, PromptOverrides.none());
32+
String example = fragment.substring(fragment.indexOf('{'), fragment.lastIndexOf('}') + 1);
33+
34+
RecoverSchema rs = new RecoverSchema(Format.JSON, "Answer", List.of(
35+
FieldSpec.scalar("text", FieldKind.STRING, true),
36+
FieldSpec.enumField("confidence", true, List.of("HIGH"), Map.of())));
37+
RecoverOutcome o = Recover.recover(example, rs, RecoverOptions.defaults());
38+
assertTrue("round-trip complete (no lostRequired): " + o.report().lostRequired(),
39+
o.report().lostRequired().isEmpty());
40+
}
41+
42+
@Test public void roundTripXmlExampleOnly() {
43+
OutputFormatSpec spec = new OutputFormatSpec(Format.XML, "Answer", PromptStyle.EXAMPLE_ONLY, List.of(
44+
new PromptField("text", FieldKind.STRING, true, false, null, null, "hi", null, null),
45+
new PromptField("confidence", FieldKind.ENUM, true, false, List.of("HIGH"), null, "HIGH", null, null)));
46+
String fragment = OutputFormatRenderer.render(spec, PromptOverrides.none());
47+
RecoverSchema rs = new RecoverSchema(Format.XML, "Answer", List.of(
48+
FieldSpec.scalar("text", FieldKind.STRING, true),
49+
FieldSpec.enumField("confidence", true, List.of("HIGH"), Map.of())));
50+
RecoverOutcome o = Recover.recover(fragment, rs, RecoverOptions.defaults());
51+
assertTrue("xml round-trip complete: " + o.report().lostRequired(), o.report().lostRequired().isEmpty());
52+
}
53+
}

0 commit comments

Comments
 (0)