You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/08/18 11:21:58 UTC

(causeway) 02/04: revises rich GraphQL value semantics plan

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch htmx
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 5653a93ab0cd7223ff8bc2eaccc80b52580bd41e
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Aug 18 11:30:50 2026 +0100

    revises rich GraphQL value semantics plan
---
 .../design.md                                      | 49 ++++++++++++++++---
 .../proposal.md                                    | 13 +++--
 .../specs/rich-graphql-value-semantics/spec.md     | 55 ++++++++++++++++++++--
 .../tasks.md                                       | 43 ++++++++++-------
 4 files changed, 127 insertions(+), 33 deletions(-)

diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
index ffb4461ce46..be9c9c20a60 100644
--- a/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
@@ -5,16 +5,31 @@ Executable reference probes showed that `LocalDateTime`, `URL`, Blob, Clob, and
 The domain member then rejects them as incompatible with its declared type.
 
 The confirmed missing standard equivalence class also contains `java.util.Date`, `java.sql.Date`, and `java.sql.Timestamp`.
+The full Causeway value-semantics inventory additionally includes scalar-shaped values, structured values, resources, sensitive values, XML DTOs, images, and tree values that need an explicit GraphQL classification even when they remain unsupported.
 Blob and Clob have specialized property output wrappers, but updates, action parameters, action results, and transfer constraints do not yet share one reversible policy.
 The archived resource-link safety change corrects malformed URLs, forbidden link exposure, metadata-versus-content policy, and authorization rechecks.
 This change reuses those established contracts.
 
+The evidence categories map to implementation as follows:
+
+| Matrix entry | Classification | Contract |
+|---|---|---|
+| `REF-VALUE-02` | Default support | Reversible built-in scalars for `LocalDateTime`, `URL`, `java.util.Date`, `java.sql.Date`, and `java.sql.Timestamp` |
+| `REF-VALUE-03` | Application extension | An explicit reversible `ScalarMarshaller`, demonstrated with `ComplexNumber`; absent registration remains output-only and input-rejecting |
+| `REF-RESOURCE-01` | Default bounded support | Structured Blob and Clob inputs plus metadata-first bounded outputs layered over value-content policy |
+| Framework value-semantics inventory | Closed classification | Explicit reversible, structured, protected, output-only, or unsupported treatment for every Causeway-provided value semantics |
+
+The previous fallback exposed all unknown values as GraphQL `String`, allowing safe non-sensitive output through GraphQL Java serialization but leaving inputs as raw Java `String` values.
+Existing Blob and Clob property reads exposed name, media type, and policy-gated download links, while property updates and action arguments still used that non-reversible fallback.
+The new contract preserves compatible non-sensitive fallback output and existing resource links while replacing fallback input and adding structured resource input.
+
 ## Goals / Non-Goals
 
 **Goals:**
 
 - Make every advertised input representation reversible.
 - Support the confirmed standard datatype equivalence class with canonical formats.
+- Close the framework-provided value-semantics inventory so every built-in is deliberately reversible, structured, protected, output-only, or unsupported.
 - Keep value semantics discoverable through standard GraphQL type introspection and existing rich datatype identities.
 - Provide safe registration points for application values.
 - Define bounded resource input and result strategies above the corrected link policy.
@@ -52,17 +67,32 @@ The change does not add a central datatype catalogue that duplicates GraphQL's t
 
 The confirmed missing standard datatypes receive documented locale-independent representations and typed coercion errors.
 `LocalDateTime` and `java.sql.Timestamp` use ISO local date-time text and preserve fractional precision without inventing an offset.
-`java.net.URL` uses the GraphQL Java `Url` scalar and its normalized external form.
+`java.net.URL` uses a documented `Url` scalar and its normalized external form, with redacted coercion errors.
 `java.util.Date` uses an ISO-8601 UTC instant, while `java.sql.Date` uses an ISO date-only value.
 Temporal precision, date-only semantics, offsets, zones, URL normalization, and SQL-versus-util distinctions are explicit.
 Existing scalar names are preserved where they already express the required semantics safely.
 
-### Keep custom values explicit
+### Keep custom values explicit and fail closed
 
-Applications register a reversible `ScalarMarshaller` and application-defined GraphQL scalar for scalar-shaped custom values.
+Applications register a `ScalarMarshaller` and application-defined GraphQL scalar for scalar-shaped custom values.
+A marshaller is output-only by default and must explicitly opt into input after establishing reversible coercion for its declared Java type.
+This behavioural compatibility break is binary-safe for existing implementations: previously compiled marshallers remain usable for output but no longer imply input capability.
 Applications needing structured custom input or output continue to replace the `TypeMapper` SPI explicitly rather than invoking constructor discovery.
-The fallback marshaller remains output-only for compatibility and maps inputs to a documented unsupported scalar that always rejects coercion before domain invocation.
-Opaque values therefore remain unsupported for input rather than falling back to `toString()` or arbitrary constructor discovery.
+The fallback marshaller maps inputs to a documented unsupported scalar that always rejects coercion before domain invocation.
+Unknown output also uses a non-disclosing unsupported representation and never invokes arbitrary `toString()` by default.
+An explicit `LEGACY_STRING` policy temporarily restores legacy output while applications register safe output-only or reversible marshallers.
+Opaque values therefore remain unsupported rather than falling back to constructor discovery or implicit textual disclosure.
+
+### Classify every built-in value semantics
+
+Framework-provided value semantics are maintained as a closed GraphQL inventory.
+Scalar-shaped values such as `java.sql.Time`, `Locale`, `Bookmark`, and `ApplicationFeatureId` receive canonical reversible scalars.
+`Password` receives protected input coercion and suppressed output.
+`Markup` is explicit output-only content.
+`LocalResourcePath` receives a structured representation preserving both path and open strategy.
+Blob and Clob retain their structured bounded resource contract.
+Composite identifiers, schema DTOs, images, and tree values remain explicitly unsupported until dedicated representations are defined.
+An integration test compares framework value-semantics providers with this inventory so a newly introduced built-in cannot silently inherit fallback behavior.
 
 ### Layer resource transfer above safe references
 
@@ -78,7 +108,8 @@ Password and hidden values never enter the generic resource path.
 
 ## Risks / Trade-offs
 
-- [Stricter fallback behavior may break applications] → Inventory affected logical types, provide diagnostics and a compatibility period, and document registration paths.
+- [Stricter fallback behavior may break applications] → Inventory affected logical types, default to non-disclosing behavior, provide an explicit temporary legacy-output policy, and document registration paths.
+- [A newly introduced Causeway value may bypass deliberate review] → Compare framework value-semantics providers against a closed GraphQL classification in integration tests.
 - [Multiple Java types may share GraphQL String] → Preserve existing rich datatype identity and document canonical scalar semantics.
 - [Resource transfer can create security and memory risks] → Require the resource-link safety capability and enforce authorization, media, size, and bounded-content policy.
 - [Custom value support can become unconstrained serialization] → Require explicit bidirectional registration and never infer from arbitrary constructors or `toString()`.
@@ -88,10 +119,14 @@ Password and hidden values never enter the generic resource path.
 Add standard marshallers ahead of generic fallback.
 Replace fallback input coercion immediately with the documented unsupported scalar because the established behavior never reconstructed the declared Java value successfully.
 Retain existing safe output fields where they are non-sensitive and label them output-only when no inverse exists.
-Existing custom `ScalarMarshaller` beans remain compatible and become the explicit registration path for reversible scalar-shaped values.
+Existing custom `ScalarMarshaller` beans remain binary compatible but become output-only until they explicitly declare reversible input support.
+Unknown output becomes non-disclosing by default; applications that need a migration interval may select `LEGACY_STRING` while registering explicit safe marshallers.
 
 ## Resolved Questions
 
 - Missing standard temporal equivalence classes use dedicated documented scalars while existing safe scalar identities remain unchanged.
 - Blob and Clob input use generated structured input objects with configurable one-mebibyte limits; bounded action-result content uses generated metadata output objects.
 - Scalar-shaped application values use application-defined scalars through `ScalarMarshaller`; structured application values require an explicit `TypeMapper` replacement.
+- Input capability is an explicit opt-in and never follows merely from registering a marshaller.
+- Every framework-provided value semantics has a closed GraphQL classification, while future structured support can be added without reopening implicit fallback.
+- Unknown output is non-disclosing by default, with `LEGACY_STRING` available only as a temporary explicit migration policy.
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
index 7d1b6140843..3086b25700f 100644
--- a/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
@@ -2,7 +2,9 @@
 
 The executable reference-application analysis confirmed that generic GraphQL `String` fallback does not reconstruct declared Java values for `LocalDateTime`, `URL`, Blob, Clob, or a custom `ComplexNumber`.
 The same source equivalence class includes `java.util.Date`, `java.sql.Date`, and `java.sql.Timestamp`, for which the current viewer has no explicit marshaller.
+A wider inventory shows that Causeway also provides value semantics for types such as `java.sql.Time`, `Locale`, `Bookmark`, `ApplicationFeatureId`, `LocalResourcePath`, `Markup`, `Password`, identifiers, images, schema DTOs, and tree values.
 Clients need reversible schema shapes and explicit extension or rejection behavior rather than input fields that accept strings and fail only during invocation.
+Every framework-provided value semantics also needs a closed GraphQL classification so newly added built-ins cannot silently inherit generic fallback behavior.
 
 The evidence is recorded in `coverage-matrix.yaml` entries `REF-VALUE-02`, `REF-VALUE-03`, and `REF-RESOURCE-01`.
 Resource URL and policy safety are provided by the archived `fix-rich-graphql-resource-link-safety` change under entries `REF-RESOURCE-02` and `REF-RESOURCE-03`.
@@ -10,8 +12,10 @@ This change builds on its same-origin `ResourcePath`, independent value-content
 
 ## What Changes
 
-- Add canonical reversible marshalling for the confirmed missing standard reference-application datatype equivalence class.
-- Replace silent input-capable object-string fallback with explicit supported, output-only, or unsupported behavior.
+- Add canonical reversible marshalling for the confirmed missing standard reference-application datatype equivalence class and the remaining scalar-shaped Causeway built-ins.
+- Classify every framework-provided value semantics as reversible, structured, protected, output-only, or unsupported, with coverage that fails when a new built-in lacks a classification.
+- Replace silent input-capable object-string fallback with explicit opt-in support; custom marshallers are output-only unless they declare reversible input.
+- Replace implicit unknown-value `toString()` output with a non-disclosing unsupported representation by default, retaining legacy string output only as an explicit migration policy.
 - Make canonical value formats and GraphQL shapes discoverable through the generated scalar, input, output, and existing rich datatype identities.
 - Define extension contracts for Causeway and application custom values without inferring constructors from `toString()`.
 - Define consistent Blob, Clob, file, and accepted resource input or result semantics above the corrected resource-link policy.
@@ -30,7 +34,8 @@ None.
 
 ## Impact
 
-- Affects GraphQL scalar marshallers, type mapping, property mutation, action invocation and results, resource transfer strategy, tests, and documentation.
+- Affects GraphQL scalar marshallers, type mapping, property mutation, action invocation and results, resource transfer strategy, unsupported-output configuration, tests, and documentation.
 - Depends on completed reference-application analysis, corrected object argument conversion, and corrected resource-link safety.
-- May require a compatibility mode for applications relying on arbitrary raw-string fallback input.
+- Deliberately changes custom marshaller input capability to fail closed and requires reversible marshallers to opt in explicitly.
+- Deliberately changes unknown output to fail closed; applications temporarily relying on arbitrary `toString()` output can opt into the documented legacy policy.
 - Does not implement browser editors, resource widgets, member metadata, or collection windowing.
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
index f8f6873e85a..d56ca3a65cd 100644
--- a/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
@@ -27,12 +27,17 @@ Supported rich value semantics SHALL be discoverable through generated GraphQL s
 - **AND** no duplicate global datatype catalogue is required
 
 ### Requirement: Canonical standard datatype support
-The default viewer SHALL provide deterministic coercion and serialization for the standard datatype set accepted from matrix entry `REF-VALUE-02`.
+The default viewer SHALL provide deterministic coercion and serialization for the standard datatype set accepted from matrix entry `REF-VALUE-02` and the remaining scalar-shaped Causeway built-ins selected by the closed classification.
 
 #### Scenario: Temporal or URL value is valid
 - **WHEN** an accepted temporal or URL value uses its canonical representation
 - **THEN** input and output preserve the documented value, precision, date, offset, zone, and normalization semantics that apply
 
+#### Scenario: Protected or presentation value is mapped
+- **WHEN** a supported password, markup, or local-resource-path value is encountered
+- **THEN** password output remains suppressed, markup remains output-only, and local resource paths preserve path and open strategy
+- **AND** none receives broader capability through generic fallback
+
 #### Scenario: Standard value is malformed
 - **WHEN** a client submits malformed canonical input
 - **THEN** GraphQL returns a typed coercion error without invoking the domain member
@@ -41,9 +46,14 @@ The default viewer SHALL provide deterministic coercion and serialization for th
 Applications SHALL be able to register an explicit reversible marshaller and GraphQL type mapping for a custom value.
 
 #### Scenario: Custom strategy is registered
-- **WHEN** an application registers compatible input and output behavior
+- **WHEN** an application registers compatible output behavior and explicitly opts into reversible input
 - **THEN** the rich schema advertises and uses that strategy for supported reads and inputs
 
+#### Scenario: Custom strategy does not opt into input
+- **WHEN** an existing or new application marshaller does not explicitly declare reversible input capability
+- **THEN** the marshaller remains available for its declared output
+- **AND** rich GraphQL reports its input as unsupported before domain invocation
+
 #### Scenario: Custom strategy is absent
 - **WHEN** an opaque custom value has no registered strategy
 - **THEN** input is reported as unsupported rather than serialized and reconstructed through `toString()` guesses
@@ -64,6 +74,36 @@ Supported resource values SHALL use a consistent metadata and bounded transfer c
 - **THEN** no content is disclosed or passed to the domain member
 - **AND** the client receives an explicit capability or validation outcome
 
+### Requirement: Closed built-in value classification
+Every framework-provided Causeway value semantics SHALL have an explicit rich GraphQL classification as reversible, structured, protected, output-only, or unsupported.
+
+#### Scenario: Framework value is known
+- **WHEN** the framework supplies a value-semantics provider
+- **THEN** GraphQL applies its reviewed classification instead of generic fallback
+- **AND** automated coverage records that classification
+
+#### Scenario: Framework adds a value semantics
+- **WHEN** a new framework-provided value-semantics provider has no GraphQL classification
+- **THEN** classification coverage fails before release
+- **AND** the new value cannot silently gain input or textual output capability
+
+### Requirement: Unknown values fail closed
+An unknown value without an explicit GraphQL strategy SHALL NOT use implicit input reconstruction or arbitrary `toString()` output by default.
+
+#### Scenario: Unknown value is used as input
+- **WHEN** an unknown value is declared by an input-capable member
+- **THEN** its GraphQL input type rejects coercion without invoking the domain member
+
+#### Scenario: Unknown value is returned
+- **WHEN** an unknown value has no explicit output strategy
+- **THEN** GraphQL returns only the documented non-disclosing unsupported representation
+- **AND** it does not invoke the unknown value's `toString()` method
+
+#### Scenario: Legacy output migration is selected
+- **WHEN** an application explicitly selects the legacy string-output policy
+- **THEN** unknown output uses the previous deterministic string behavior
+- **AND** input remains unsupported
+
 ### Requirement: Sensitive values remain undisclosed
 Passwords, hidden values, and other configured sensitive values SHALL NOT be exposed through generic serialization, metadata, resources, diagnostics, or errors.
 
@@ -75,7 +115,12 @@ Passwords, hidden values, and other configured sensitive values SHALL NOT be exp
 ### Requirement: Value compatibility policy
 Changes to generic fallback behavior SHALL include compatibility diagnostics and a documented migration path.
 
-#### Scenario: Existing application relies on fallback input
-- **WHEN** strict reversible-input enforcement detects an affected logical type
+#### Scenario: Existing application marshaller relied on implicit input
+- **WHEN** strict reversible-input enforcement detects a marshaller that did not opt into input
 - **THEN** the application receives an actionable diagnostic and registration path
-- **AND** the configured compatibility behavior is deterministic
+- **AND** the marshaller continues to provide output without receiving unreviewed input capability
+
+#### Scenario: Existing application relies on fallback output
+- **WHEN** strict output enforcement detects a logical type without an explicit strategy
+- **THEN** the application receives an actionable registration path
+- **AND** any temporary legacy string behavior requires explicit configuration
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
index 72c8a585c2d..ee056d3b198 100644
--- a/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
@@ -1,28 +1,37 @@
 ## 1. Confirm Scope from Analysis
 
-- [ ] 1.1 Map matrix entries `REF-VALUE-02`, `REF-VALUE-03`, and `REF-RESOURCE-01` to default support, application extension, output-only support, or explicit rejection.
-- [ ] 1.2 Record compatibility exposure from current generic-string fallback and existing Blob or Clob shapes.
-- [ ] 1.3 Verify prerequisites from object-interaction correctness and resource-link safety are complete.
+- [x] 1.1 Map matrix entries `REF-VALUE-02`, `REF-VALUE-03`, and `REF-RESOURCE-01` to default support, application extension, output-only support, or explicit rejection.
+- [x] 1.2 Record compatibility exposure from current generic-string fallback and existing Blob or Clob shapes.
+- [x] 1.3 Verify prerequisites from object-interaction correctness and resource-link safety are complete.
 
 ## 2. Reversible Value Contracts
 
-- [ ] 2.1 Select standard GraphQL scalar or structured shapes and canonical formats for every accepted standard datatype equivalence class.
-- [ ] 2.2 Require reversible input coercion for every advertised property and action input.
-- [ ] 2.3 Add canonical marshallers for the accepted temporal, URL, and related reference-app types.
-- [ ] 2.4 Add actionable bounded diagnostics for output-only and unsupported values.
-- [ ] 2.5 Define and test application marshaller plus GraphQL type-mapping registration.
+- [x] 2.1 Select standard GraphQL scalar or structured shapes and canonical formats for every accepted standard datatype equivalence class.
+- [x] 2.2 Require reversible input coercion for every advertised property and action input.
+- [x] 2.3 Add canonical marshallers for the accepted temporal, URL, and related reference-app types.
+- [x] 2.4 Add actionable bounded diagnostics for output-only and unsupported values.
+- [x] 2.5 Define and test application marshaller plus GraphQL type-mapping registration.
 
 ## 3. Resource Value Contracts
 
-- [ ] 3.1 Define common Blob, Clob, file, and other accepted resource metadata above the corrected resource-link contract.
-- [ ] 3.2 Implement safe property read, update, action-parameter, and action-result behavior for supported resources.
-- [ ] 3.3 Enforce accepted-file, media-type, size, authorization, and bounded-transfer policies.
-- [ ] 3.4 Define explicit unsupported behavior for password, hidden, opaque, tree, and custom values lacking a registered strategy.
+- [x] 3.1 Define common Blob, Clob, file, and other accepted resource metadata above the corrected resource-link contract.
+- [x] 3.2 Implement safe property read, update, action-parameter, and action-result behavior for supported resources.
+- [x] 3.3 Enforce accepted-file, media-type, size, authorization, and bounded-transfer policies.
+- [x] 3.4 Define explicit unsupported behavior for password, hidden, opaque, tree, and custom values lacking a registered strategy.
 
 ## 4. Compatibility and Verification
 
-- [ ] 4.1 Add query, mutation, action, nullability, invalid-input, precision, timezone, and round-trip tests for every supported category.
-- [ ] 4.2 Add security tests proving sensitive values cannot appear through output fallback, metadata, errors, resources, or diagnostics.
-- [ ] 4.3 Add schema snapshots and migration tests for affected existing clients.
-- [ ] 4.4 Document canonical formats, standard introspection, resource strategies, extension registration, compatibility behavior, and exclusions.
-- [ ] 4.5 Run GraphQL model and viewer tests, reference-derived fixtures, documentation checks, formatting, and strict OpenSpec validation.
+- [x] 4.1 Add query, mutation, action, nullability, invalid-input, precision, timezone, and round-trip tests for every supported category.
+- [x] 4.2 Add security tests proving sensitive values cannot appear through output fallback, metadata, errors, resources, or diagnostics.
+- [x] 4.3 Add schema snapshots and migration tests for affected existing clients.
+- [x] 4.4 Document canonical formats, standard introspection, resource strategies, extension registration, compatibility behavior, and exclusions.
+- [x] 4.5 Run GraphQL model and viewer tests, reference-derived fixtures, documentation checks, formatting, and strict OpenSpec validation.
+
+## 5. Fail-Closed Built-In Coverage Delta
+
+- [ ] 5.1 Inventory and classify every framework-provided `ValueSemanticsProvider` as reversible, structured, protected, output-only, or unsupported.
+- [ ] 5.2 Make `ScalarMarshaller` input support an explicit opt-in while keeping existing implementations binary-safe and output-capable.
+- [ ] 5.3 Replace implicit unknown-value `toString()` output with a non-disclosing default and an explicit temporary `LEGACY_STRING` migration policy.
+- [ ] 5.4 Add canonical mappings for the remaining selected scalar, password, markup, and local-resource-path built-ins while explicitly rejecting composite, image, DTO, and tree values.
+- [ ] 5.5 Add closed-inventory, migration, introspection, round-trip, and non-disclosure tests plus corresponding configuration and user documentation.
+- [ ] 5.6 Regenerate the schema and rerun GraphQL model, viewer, reference-derived, documentation, formatting, approval, and strict OpenSpec validation checks.