Skip to content

Restore resolveLanguage alongside resolveLiteral#865

Merged
lucksus merged 9 commits into
refactor/typed-rdf-literals-and-fn-cleanupfrom
feat/restore-resolve-language-with-resolve-literal
Jun 19, 2026
Merged

Restore resolveLanguage alongside resolveLiteral#865
lucksus merged 9 commits into
refactor/typed-rdf-literals-and-fn-cleanupfrom
feat/restore-resolve-language-with-resolve-literal

Conversation

@lucksus

@lucksus lucksus commented Jun 18, 2026

Copy link
Copy Markdown
Member

based on #842

Why

That branch's resolveLanguageresolveLiteral refactor (073cfd4) collapsed two distinct concepts into one boolean and removed resolveLanguage as a public API. That's a breaking change for dev: existing @Property({ resolveLanguage: "..." }) user code silently stops working, and the general ability to resolve a property's value through an arbitrary language (not just literal) was lost — values could only be stored as deterministic literals or as signed envelopes on the literal language.

This PR brings resolveLanguage back as the first-class, general mechanism it is in dev, while keeping resolveLiteral as the literal-only storage optimization it was introduced to be. Both coexist.

Model

  • resolveLanguage?: string (default "literal") — the general selector. A custom language address routes values through expression_create on that language, producing signed-envelope URIs ({author, timestamp, data, proof}). This is the dev behavior, unchanged.
  • resolveLiteral?: boolean (default true) — optimization for the literal language only: true stores deterministic literal:* IRIs (POS-index friendly); false routes the literal value through expression_create. Ignored when resolveLanguage is a custom address.

Write precedence: custom language → expression_create(lang); literal + resolveLiteral: false → expression_create("literal"); otherwise → deterministic literal: IRI.

A custom resolveLanguage is never treated as deterministic-literal storage, so WHERE matching correctly uses FILTER(STR(?v)=…) on decoded values rather than a typed-literal VALUES clause.

Changes (one commit per layer)

  • Rust data model + transport — resolve_language re-added to ShapeProperty / PropertyShape; SHACL writer emits both ad4m://resolveLanguage and ad4m://resolveLiteral; shape.rs loaders populate both (and derive resolve_literal from a legacy resolveLanguage when the explicit flag is
    absent).
  • Rust write path — get_resolve_language_from_shacl restored; resolve_property_value implements the unified precedence.
  • Rust read/query path — new ShapeProperty::is_deterministic_literal() drives WHERE building (sparql_builder, projection) and resolve_language_transforms, which again fetches+transforms any non-deterministic-literal property (custom language or resolveLiteral: false).
  • Rust MCP — ShaclProperty.resolve_language + resolve_property_resolve_language restored beside the literal equivalents.
  • TS core (decorators, types, sdna, shacl-gen, json-schema, SHACLShape, Ad4mModel, query-sparql, query-utils, PerspectiveProxy) — resolveLanguage is a @Property option again; SDNA emits property_resolve_language with the actual language; write path + query builders apply the same
    unified precedence as Rust.

Backward compatibility

  • Old persisted perspectives carrying only ad4m://resolveLanguage still load: "literal" → resolveLiteral: true, any other language → false. Handled on both TS and Rust read paths.
  • An explicit empty-string resolveLanguage is preserved verbatim (Some("")), matching dev — not coerced to None.

Tests

  • Rust: 640 passed / 0 failed. New round-trip tests for resolve_literal true/false, custom resolve_language, and empty-string preservation, asserting is_deterministic_literal() classification.
  • Core TS: 260 passed / 0 failed; tsc --noEmit clean. New SHACLShape round-trip tests for resolveLanguage (via toJSON/fromJSON and toLinks/fromLinks) and the resolveLanguage + resolveLiteral pairing.

Review notes — two design calls

  1. resolveLiteral is meaningful only when resolveLanguage is "literal"/unset; a custom language always produces envelopes regardless of the flag.
  2. Empty-string resolveLanguage is preserved rather than dropped (pinned by round_trip_preserves_empty_resolve_language).

🤖 Generated with Claude Code

lucksus and others added 6 commits June 18, 2026 21:29
…data model

Restores resolve_language (general expression-language selector) as a
first-class field on ShapeProperty and PropertyShape, kept beside the
resolve_literal optimization flag added on this branch.

- types.rs / shacl_parser.rs: resolve_language: Option<String> re-added.
- parse_shacl_to_links: emits both ad4m://resolveLanguage and
  ad4m://resolveLiteral links when set.
- shape.rs (load_shape + load_shape_from_meta): populates resolve_language
  from RDF/meta; resolve_literal still derived from resolveLanguage for
  backward compat when the explicit flag is absent.

resolve_language is the dev mechanism (route values through any language);
resolve_literal is the literal-only deterministic-storage optimization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore get_resolve_language_from_shacl (string selector) beside
get_resolve_literal_from_shacl. resolve_property_value now applies the
unified precedence:

  1. custom resolve_language (!= "literal") -> expression_create(lang)
     -> signed-envelope URI (the dev behavior, lenient fallback on error)
  2. literal language + resolveLiteral: false -> expression_create("literal")
  3. otherwise -> deterministic literal: IRI (optimized default)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lve_literal

Add ShapeProperty::is_deterministic_literal() — true only for the literal
language (explicit or default) with resolve_literal != Some(false). Custom
languages always produce signed envelopes, so they are never deterministic.

- sparql_builder / projection: WHERE matching uses is_deterministic_literal()
  to decide typed-literal VALUES vs FILTER-on-decoded probing.
- query.rs resolve_language_transforms: now fetches+transforms any
  non-deterministic-literal property (custom language OR resolveLiteral:false),
  restoring dev's general expression-resolution read path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ShaclProperty carries resolve_language beside resolve_literal again, read
from ad4m://resolveLanguage in load_class_properties_with_uri. Add
resolve_property_resolve_language helper beside resolve_property_resolve_literal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… layer

resolveLanguage (general expression-language selector, default "literal") is
a first-class @Property option again, kept alongside the resolveLiteral
literal-storage optimization added on this branch.

- decorators / types: resolveLanguage?: string re-added; Property() defaults
  it to "literal", resolveLiteral to true.
- sdna: emits property_resolve_language with the actual language again.
- shacl-gen / SHACLShape / json-schema: carry + round-trip both fields
  (ad4m://resolveLanguage and ad4m://resolveLiteral links).
- Ad4mModel write path + query builders (query-sparql, query-utils,
  PerspectiveProxy): unified precedence — custom language -> createExpression
  on that language; literal + resolveLiteral:false -> createExpression on
  literal; else deterministic literal: IRI. Custom languages are never
  treated as deterministic-literal storage in WHERE building.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…empty-string

- Rust: struct-literal test fixtures carry both fields; shape.rs preserves an
  explicit empty resolve_language verbatim (matching dev) rather than coercing
  to None. New round_trip tests for resolve_literal true/false and a custom
  resolve_language, asserting is_deterministic_literal() classification.
- integration test exercises the non-literal FILTER path via a custom
  resolveLanguage on the target shape.
- TS: SHACLShape round-trip tests for resolveLanguage (toJSON/fromJSON and
  toLinks/fromLinks) and the resolveLanguage+resolveLiteral pairing.

Full Rust suite (640) and core TS suite (260) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 376afe90-8b00-4689-8f99-a14235fe4c80

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/restore-resolve-language-with-resolve-literal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

HexaField and others added 3 commits June 19, 2026 12:37
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… deterministic literals

Two read/write-path gaps surfaced by the resolveLanguage+resolveLiteral
integration tests:

1. resolve_property_value (write, createSubject / MCP path) only encoded
   String and Number for deterministic-literal properties; Bool, Object and
   Array fell through to `value.to_string()` and were stored as opaque raw
   targets (`false`, `{...}`) instead of `literal:boolean:` / `literal:json:`
   IRIs. The typed-literal storage layer then kept them as NamedNodes, so
   reads returned strings and indexed WHERE filters never matched. Encode
   Bool as `literal:boolean:{b}` and Object/Array via Literal::from_json,
   matching the TS `valueToLiteralIri` encoding.

2. resolve_language_transforms skipped deterministic-literal properties, so a
   property with `resolveLanguage: "literal"` plus a transform never had its
   transform applied on read. Include properties that carry a transform, and
   use the already-decoded hydrated value as the transform focus (never
   re-interpreting it as an expression URL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th-resolve-literal' into feat/restore-resolve-language-with-resolve-literal
@lucksus lucksus merged commit 691d92c into refactor/typed-rdf-literals-and-fn-cleanup Jun 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants