Skip to content

Commit fb2c22d

Browse files
dmealingclaude
andcommitted
fix(extract): a fenced answer is not discarded for an earlier object or a brace in prose
Closes #363. The JSON locator behind `extract` took the first object that parsed, in every port. A draft object in the model's reasoning, an echoed format example (`{"topic": "<slug>"}`) or prose with a brace (`{a set}`) therefore won, the complete answer in the following ```json fence was never read, and with its @required fields missing the reply was reported unusable: silently discarded, not mis-parsed. Selection now searches every fenced block first, then the whole reply, and takes the first object that carries at least one declared field. A fenced object with none of them (a fenced example) falls through the same way, so it cannot shadow an unfenced answer either. When no object carries a declared field, the old first-closed-else-first-open rule decides, so every reply that parsed before parses the same and all 33 existing corpus cases keep their outcome. Five new extract-conformance cases, from the issue's rows plus the two fall-through directions, fail on the old locator and pass in all four engines (Kotlin runs the Java one): TS 164, Java 298, C# 296, Python 204 extract tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M1mRRB1K2WdYy82kntMQdx
1 parent ca784f4 commit fb2c22d

33 files changed

Lines changed: 312 additions & 15 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ here.**
1010

1111
## [Unreleased]
1212

13+
### Fixed
14+
15+
- **`extract` no longer discards a fenced answer that follows an earlier object or a brace in
16+
prose ([#363]), in every port.** The JSON locator took the first object that parsed, so a
17+
draft object in the model's reasoning, an echoed format example (`{"topic": "<slug>"}`) or
18+
plain prose with a brace (`{a set}`) won, and the complete answer in the following
19+
```` ```json ```` fence was never read; with its `@required` fields missing, the reply
20+
was reported unusable. The locator now searches fenced blocks first, then the whole reply,
21+
and takes the first object that carries at least one declared field. A fenced object with
22+
none of them (a fenced example) falls through the same way. When no object carries a
23+
declared field, the old first-object rule decides, so every reply that parsed before parses
24+
the same. Five new `extract-conformance` cases pin the behaviour in all four engines
25+
(Kotlin runs the Java one).
26+
27+
[#363]: https://github.com/metaobjectsdev/metaobjects/issues/363
28+
1329
## [1.0.6] — 2026-09-23
1430

1531
_Maven Central `8.0.6` only. npm, PyPI and NuGet have no product change and stay at `1.0.5`

‎docs/CONFORMANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
3131
| [`fixtures/verify-conformance/`](../fixtures/verify-conformance/) | 31 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
3232
| [`fixtures/verify-strict-conformance/`](../fixtures/verify-strict-conformance/) | 1 | ✓ | — | — | — | ✓ |
3333
| [`fixtures/render-conformance/`](../fixtures/render-conformance/) | 15 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
34-
| [`fixtures/extract-conformance/`](../fixtures/extract-conformance/) | 33 | ✓ | ✓ | inherits the shared JVM engine | ✓ | ✓ |
34+
| [`fixtures/extract-conformance/`](../fixtures/extract-conformance/) | 38 | ✓ | ✓ | inherits the shared JVM engine | ✓ | ✓ |
3535
| [`fixtures/output-prompt-conformance/`](../fixtures/output-prompt-conformance/) | 14 | ✓ | ✓ | ✓ | ✓ | ✓ |
3636
| [`fixtures/persistence-conformance/`](../fixtures/persistence-conformance/) | 33 (27 query + 6 migration) | all 33 | 27 query (migrations TS-only, ADR-0015) | 27 query (via Exposed) | 27 query | 27 query |
3737
| [`fixtures/api-contract-conformance/`](../fixtures/api-contract-conformance/) | 61 (31 core + 10 tph + 9 m2m + 2 jsonb + 2 write-through + 7 projection) | ✓ (Fastify reference + generated lane) | ✓ (embedded HTTP + JDBC) | ✓ (embedded HTTP + Exposed) | ✓ (HttpListener + Npgsql) | ✓ (FastAPI + pg8000) |

‎fixtures/extract-conformance/README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ Each `fields[]` entry: `name`, `kind` (`STRING|INT|LONG|DOUBLE|BOOLEAN|ENUM|OBJE
4444

4545
FR-011 added the `enum-*`, `nested-object-*`, `array-of-objects`, `xml-nested`, and
4646
`multi-malformation` cases on top of the original FR-010 ten.
47+
48+
#363 added the three `json-fence-after-*` cases, `json-fenced-example-then-answer` and
49+
`json-shapeless-object-then-answer` cases: JSON selection searches fenced blocks first, then
50+
the whole reply, and takes the first object carrying at least one declared field; only when
51+
none does is the first object taken.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "empty": false, "states": { "topic": "EXTRACTED", "worth_forming": "EXTRACTED", "reason": "EXTRACTED" }, "data": { "topic": "real", "worth_forming": true, "reason": "r" } }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{"topic":"draft_idea"}
2+
```json
3+
{"topic":"real","worth_forming":true,"reason":"r"}
4+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{ "format": "JSON", "rootName": "gate", "fields": [
2+
{ "name": "topic", "kind": "STRING", "required": false },
3+
{ "name": "worth_forming", "kind": "BOOLEAN", "required": true },
4+
{ "name": "reason", "kind": "STRING", "required": true } ] }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "empty": false, "states": { "topic": "EXTRACTED", "worth_forming": "EXTRACTED", "reason": "EXTRACTED" }, "data": { "topic": "real", "worth_forming": true, "reason": "r" } }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The format is {"topic": "<slug>"}.
2+
```json
3+
{"topic":"real","worth_forming":true,"reason":"r"}
4+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{ "format": "JSON", "rootName": "gate", "fields": [
2+
{ "name": "topic", "kind": "STRING", "required": false },
3+
{ "name": "worth_forming", "kind": "BOOLEAN", "required": true },
4+
{ "name": "reason", "kind": "STRING", "required": true } ] }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "empty": false, "states": { "topic": "EXTRACTED", "worth_forming": "EXTRACTED", "reason": "EXTRACTED" }, "data": { "topic": "real", "worth_forming": true, "reason": "r" } }

0 commit comments

Comments
 (0)