Correctness fixes from full-repo review: query semantics, element projection, deletion, status codes - #28
Open
blake-regalia wants to merge 11 commits into
Open
Correctness fixes from full-repo review: query semantics, element projection, deletion, status codes#28blake-regalia wants to merge 11 commits into
blake-regalia wants to merge 11 commits into
Conversation
The schema defines 'value' as an array (or null), and commit 9e93353 made deserialization accept that shape — but execution still only honored the first array element, the null-value branch compared a List against JsonNull (dead code), a JSON null value was rejected outright, and every constraint added a required triple pattern so 'or' composites over different properties silently excluded elements missing either property. - '=' (and the schema's 'in', now accepted) is set membership over all values in the array - 'value': null and null array entries match elements lacking the property, via OPTIONAL + !bound() - every constraint pattern is OPTIONAL with a bound() guard, so or-composites over different properties work - property names are sanitized before becoming SPARQL variable names - POST/PUT queries missing select/where return 400 instead of a 500 NPE Adds QueryResultsTest — the first execution coverage of POST /query-results, GET /queries/{id}/results, and the constraint translation (equality, membership, @type, null, and/or composites). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two silent data-loss paths around array-valued properties: - extractModelElementToJson skipped any multi-valued sysml: property that had no json: annotation triple, so models ingested as raw RDF (rather than through this API's commit path, which writes the annotation) lost properties like featureChain from every /elements response with no error. Emit a best-effort array instead; when an annotation exists it remains authoritative, and the skip no longer depends on predicate iteration order. - a commit change whose identity @id disagreed with its payload @id was silently discarded while the commit still returned 200; now a 400. Adds ElementArrayTest: array round-trip through the API (order preserved via annotation), the annotation-less raw-RDF load path, and the identity/payload mismatch rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The commit handler collected deleted-element nodes into deleteIncoming
but never used it: only the deleted element's own (outgoing) triples
were removed, so every element that referenced it kept a dangling
{"@id": <deleted>} forever.
The update now also deletes incoming link triples and the referencing
property's json: array annotation (which cannot be rewritten in SPARQL;
the surviving link triples render as a best-effort array via the
annotation-less fallback path).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The header-forwarding block inside the respondText configure lambda read from the outgoing (empty) content headers and discarded the headersOf() value it built — dead code, so ETag, Location, WWW-Authenticate, etc. from layer1 never reached clients (401s arrived without a challenge). Append upstream headers to the response before responding, excluding content headers (managed by respondText) and this server's Date/Server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GET of a nonexistent element returned 200 with a fabricated
{"@type": null} body; now 404
- POST /commits and query-results against a nonexistent project crashed
on next()!! over an empty model (500); layer1 failures are now
forwarded (404) and an empty repo result falls back safely
- extra keys next to an @id reference threw a bare java.lang.Error
(500); now InvalidSysmlSerializationError (400)
- blank-node subjects in model graphs NPE-crashed every list endpoint
(elements/roots/relationships/query results); they are now skipped
Adds NegativePathTest covering the 404/400 surfaces.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…is 409 Deletion only set a urn:sysmlv2:deleted flag that list endpoints filtered — GET-by-id still returned deleted projects/branches/tags with 200. Single-resource GETs now apply the same filter. POST /projects with an existing @id silently overwrote the project via the underlying PUT (replacing title/description/defaultBranch); it now returns 409 Conflict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A payload string containing \r produced an illegal SPARQL string literal and failed the whole commit with an opaque upstream parse error. Element @ids (subject and references) were interpolated into IRI positions unvalidated — hostile or merely malformed ids now get a 400 up front, and an array member without @id no longer NPEs into a 500. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Branch/tag/project builders defaulted a missing dct:title to "" and a missing mms:created to OffsetDateTime.now(), so broken upstream data (e.g. the GraphDB wildcard-CONSTRUCT bug behind issue #20) surfaced as plausible-looking invented values instead of an observable failure. Malformed resources are now omitted (404 on single GET) rather than silently patched up. Adds BranchListPairingTest asserting each branch's name stays paired with its own @id in the list response — the exact regression surface reported in #20. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ktor 3.4.3 -> 3.5.1, Jena 6.0.0 -> 6.1.0, Kotest 6.1.11 -> 6.2.2, logback 1.5.18 -> 1.5.38, kotlinx-coroutines-test 1.10.2 -> 1.11.0, java-jwt 4.5.0 -> 4.6.0, JaCoCo 0.8.12 -> 0.8.15. All API-compatible; full suite green. Major migrations (Gradle 9, Kotlin 2.4, Sonar plugin 7, JUnit 6) deliberately deferred to a dedicated change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CORS was anyHost() with allowCredentials = true (flagged @todo), letting any website make credentialed cross-origin requests that replay a logged-in user's Authorization header. Origins are now configurable via flexo.corsAllowedOrigins / FLEXO_CORS_ALLOWED_ORIGINS (space-separated); when unset the API still answers any origin, but credentialed cross-origin requests are disabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
Eleven incremental fixes from a full review of the implementation, each committed with its own regression tests. The suite grows from 29 to 55 tests, all green against the layer1 + Fuseki test stack.
Issue fixes
PrimitiveConstraintsemantics completed per the OMG schema:=(and the schema'sin, now accepted) is set membership over the whole value array instead of silently using onlyvalue[0];"value": nulland null array entries match elements lacking the property (the old null check compared aListagainstJsonNull— dead code);valueis nullable as the schema requires;orcomposites over different properties no longer exclude elements missing either property (constraint patterns are OPTIONAL withbound()guards); property names are sanitized before becoming SPARQL variables; queries missingselect/wherereturn 400 instead of an NPE 500. Adds the first execution coverage ofPOST /query-resultsand saved-query results.sysml:properties without ajson:array annotation (e.g. models loaded as raw RDF rather than through this API's commit path) were silently dropped from every/elementsresponse — exactly the reported disappearance offeatureChain. They now render as a best-effort array; when the annotation exists it remains authoritative and preserves order. Also removes a second silent-loss path: a commit change whoseidentity.@iddisagrees with itspayload.@idwas discarded while the commit still returned 200 — now a 400.created/namedefaults that masked the upstream corruption, and a new test pins thename↔@idpairing in the list response — but the issue should stay open until a layer1 release with the explicit per-branch property binding lands and the compose files pin it.Additional correctness fixes found during review
forward()never actually propagated upstream response headers (the built header map was discarded inside therespondTextlambda) —ETag,Location, andWWW-Authenticatefrom layer1 now reach clients; forwarded 401s carry their challenge.{"@type": null}body (now 404); commits/queries against a nonexistent project crashed onnext()!!(now forwarded 404); malformed payloads threw a barejava.lang.Error(now 400); blank-node subjects NPE-crashed whole list responses (now skipped).POST /projectswith an existing@idsilently overwrote the project via the underlying PUT (now 409).\rin any string failed the whole commit with an opaque upstream parse error); element@ids (subject and references) are validated before interpolation into SPARQL IRI positions.anyHost()withallowCredentials = true: any website could make credentialed cross-origin requests replaying a logged-in user'sAuthorizationheader. Origins are now configurable viaFLEXO_CORS_ALLOWED_ORIGINS; without configuration the API still answers any origin but credentialed cross-origin requests are disabled.Housekeeping
cpcommand fix.Test plan
docker compose -f src/test/resources/docker-compose.yml up -d./gradlew test— 55 tests, 0 failures (new suites: QueryResultsTest, ElementArrayTest, DeleteElementTest, ForwardHeadersTest, NegativePathTest, SoftDeleteTest, EscapingTest, BranchListPairingTest)🤖 Generated with Claude Code