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:56 UTC

(causeway) branch htmx updated (a6f8242d7cd -> 182c578b644)

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

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


    from a6f8242d7cd archives rich GraphQL resource link safety
     new b6ec76fc08d promotes rich GraphQL value and resource semantics
     new 5653a93ab0c revises rich GraphQL value semantics plan
     new 3ade9dc479a adds rich GraphQL value and resource semantics
     new 182c578b644 archives rich GraphQL value and resource semantics

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../pages/sections/causeway.viewer.graphql.adoc    |  27 +
 .../core/config/CausewayConfiguration.java         |  49 +-
 .../.openspec.yaml                                 |   0
 .../design.md                                      | 132 +++
 .../proposal.md                                    |  16 +-
 .../specs/rich-graphql-value-semantics/spec.md     |  55 +-
 .../tasks.md                                       |  37 +
 .../planned-changes/web-component-viewer/README.md |   9 +-
 .../design.md                                      |  88 --
 .../tasks.md                                       |  28 -
 .../specs/rich-graphql-value-semantics/spec.md     |  63 +-
 .../modules/ROOT/pages/rich-value-semantics.adoc   | 199 +++++
 .../adoc/modules/ROOT/pages/spi-services.adoc      |   8 +-
 .../adoc/modules/ROOT/partials/module-nav.adoc     |   1 +
 .../applib/marshallers/ScalarMarshaller.java       |  11 +
 .../marshallers/ScalarMarshallerAbstract.java      |  36 +-
 .../domain/common/query/ObjectFeatureUtils.java    |   3 +
 .../rich/mutation/RichMutationForProperty.java     |  18 +-
 .../domain/rich/query/RichActionParamsParam.java   |  36 +
 .../domain/rich/query/RichPropertyGetBlob.java     |  30 +
 .../rich/query/RichPropertyGetBlobAbstract.java    |  14 +-
 ...ame.java => RichPropertyGetBlobByteLength.java} |  20 +-
 .../domain/rich/query/RichPropertyGetClob.java     |  34 +
 .../rich/query/RichPropertyGetClobAbstract.java    |  14 +-
 ...ame.java => RichPropertyGetClobByteLength.java} |  20 +-
 ...ava => RichPropertyGetClobCharacterLength.java} |  19 +-
 .../domain/rich/query/RichPropertyValidate.java    |  22 +-
 .../query/RichResourceMetadataField.java}          |  35 +-
 ...a => ScalarMarshallerApplicationFeatureId.java} |  21 +-
 .../marshallers/ScalarMarshallerBigDecimal.java    |   2 +-
 .../marshallers/ScalarMarshallerBigInteger.java    |   2 +-
 ...rimitive.java => ScalarMarshallerBookmark.java} |  16 +-
 .../ScalarMarshallerBooleanPrimitive.java          |   2 +-
 .../ScalarMarshallerBooleanWrapper.java            |   2 +-
 .../marshallers/ScalarMarshallerBytePrimitive.java |   2 +-
 .../marshallers/ScalarMarshallerByteWrapper.java   |   2 +-
 .../marshallers/ScalarMarshallerCharPrimitive.java |   2 +-
 .../ScalarMarshallerCharacterWrapper.java          |   2 +-
 .../ScalarMarshallerDoublePrimitive.java           |   2 +-
 .../marshallers/ScalarMarshallerDoubleWrapper.java |   2 +-
 .../ScalarMarshallerFloatPrimitive.java            |   2 +-
 .../marshallers/ScalarMarshallerFloatWrapper.java  |   2 +-
 .../marshallers/ScalarMarshallerIntPrimitive.java  |   2 +-
 .../ScalarMarshallerIntegerWrapper.java            |   2 +-
 ...apper.java => ScalarMarshallerJavaSqlDate.java} |  19 +-
 ...apper.java => ScalarMarshallerJavaSqlTime.java} |  16 +-
 ....java => ScalarMarshallerJavaSqlTimestamp.java} |  20 +-
 ...pper.java => ScalarMarshallerJavaUtilDate.java} |  24 +-
 .../marshallers/ScalarMarshallerJdk8LocalDate.java |   2 +-
 ...java => ScalarMarshallerJdk8LocalDateTime.java} |  23 +-
 .../marshallers/ScalarMarshallerJdk8LocalTime.java |   2 +-
 .../ScalarMarshallerJdk8OffsetDateTime.java        |   2 +-
 .../ScalarMarshallerJdk8OffsetTime.java            |   2 +-
 .../ScalarMarshallerJdk8ZonedDateTime.java         |   2 +-
 ...llerString.java => ScalarMarshallerLocale.java} |  18 +-
 .../marshallers/ScalarMarshallerLongPrimitive.java |   2 +-
 .../marshallers/ScalarMarshallerLongWrapper.java   |   2 +-
 ...llerString.java => ScalarMarshallerMarkup.java} |  15 +-
 .../model/marshallers/ScalarMarshallerObject.java  |  18 +-
 ...gInteger.java => ScalarMarshallerPassword.java} |  21 +-
 .../ScalarMarshallerShortPrimitive.java            |   2 +-
 .../marshallers/ScalarMarshallerShortWrapper.java  |   2 +-
 .../model/marshallers/ScalarMarshallerString.java  |   2 +-
 ...leanPrimitive.java => ScalarMarshallerUrl.java} |  25 +-
 .../model/marshallers/ScalarMarshallerUuid.java    |   2 +-
 .../types/BuiltInValueTypeClassifications.java     | 114 +++
 .../graphql/model/types/GraphQLValueScalars.java   | 292 ++++++
 .../graphql/model/types/ResourceValueTypes.java    | 436 +++++++++
 .../viewer/graphql/model/types/ScalarMapper.java   |   4 +
 .../types/ScalarMapperUsingScalarMarshallers.java  |  17 +
 .../graphql/model/types/TypeMapperDefault.java     |  18 +-
 .../domain/common/query/ResourcePathTest.java      |   4 +
 .../model/types/GraphQLValueScalarsTest.java       | 112 +++
 .../types/ScalarMarshallerInputCapabilityTest.java |  63 ++
 .../viewer/test/domain/calc/Calculator.java        | 140 +++
 .../viewer/test/domain/calc/ComplexNumber.java     |  50 ++
 .../viewer/test/domain/calc/ComplexNumberTest.java |  39 +-
 .../domain/calc/ComplexNumberValueSemantics.java   |  26 +-
 .../domain/calc/ScalarMarshallerComplexNumber.java | 105 +++
 .../viewer/test/domain/calc/UnmappedValue.java     |  14 +-
 .../test/domain/calc/UnmappedValueSemantics.java   |  37 +-
 .../viewer/test/domain/dept/StaffMember.java       |  18 +-
 ...r_IntegTest.each.built_in_value_semantics._.gql |  32 +
 ...est.each.built_in_value_semantics.approved.json |  51 ++
 ...st.each.introspects_fail_closed_built_ins._.gql |  19 +
 ...introspects_fail_closed_built_ins.approved.json |  89 ++
 ...IntegTest.each.introspects_resource_input._.gql |  19 +
 ...t.each.introspects_resource_input.approved.json |  43 +
 ...egTest.each.introspects_unsupported_input._.gql |   7 +
 ...ach.introspects_unsupported_input.approved.json |   9 +
 ...r_IntegTest.each.introspects_value_scalar._.gql |   7 +
 ...est.each.introspects_value_scalar.approved.json |   9 +
 ...tegTest.each.malformed_value_is_rejected._.gql} |   4 +-
 ....each.malformed_value_is_rejected.approved.json |  12 +
 ...Test.each.resource_media_type_is_rejected._.gql |  13 +
 ...h.resource_media_type_is_rejected.approved.json |  24 +
 ...ulator_IntegTest.each.some_locale.approved.json |   2 +-
 ...each.unsupported_value_input_is_rejected._.gql} |   4 +-
 ...supported_value_input_is_rejected.approved.json |  12 +
 ...gTest.each.value_and_resource_round_trips._.gql | 132 +++
 ...ch.value_and_resource_round_trips.approved.json | 152 ++++
 .../BuiltInValueTypeClassifications_IntegTest.java |  90 ++
 .../LegacyUnsupportedOutputPolicy_IntegTest.java}  |  78 +-
 .../test/e2e/special/ResourcePolicy_IntegTest.java |  25 +
 .../e2e/special/ResourceValuePolicy_IntegTest.java | 228 +++++
 ...ValidationDoesNotProcessOrDiscloseContent._.gql |  19 +
 ...nDoesNotProcessOrDiscloseContent.approved.json} |   8 +-
 .../viewer/test/e2e/special/Staff_2_IntegTest.java |  10 +
 .../staffmutating/StaffMutating_IntegTest.java     |  20 +-
 ...ing_IntegTest.staff_member_edit_resources._.gql |  35 +
 ...gTest.staff_member_edit_resources.approved.json |  31 +
 ....staff_member_rejects_unaccepted_resource._.gql |  12 +
 ...ember_rejects_unaccepted_resource.approved.json |  16 +
 viewers/graphql/test/src/test/resources/schema.gql | 995 ++++++++++++++++++++-
 ...ulator_IntegTest.each.some_locale.approved.json |   2 +-
 115 files changed, 4514 insertions(+), 494 deletions(-)
 rename openspec/{planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics => changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics}/.openspec.yaml (100%)
 create mode 100644 openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/design.md
 rename openspec/{planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics => changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics}/proposal.md (54%)
 copy openspec/{planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics => changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics}/specs/rich-graphql-value-semantics/spec.md (58%)
 create mode 100644 openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/tasks.md
 delete mode 100644 openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/design.md
 delete mode 100644 openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/tasks.md
 rename openspec/{planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics => }/specs/rich-graphql-value-semantics/spec.md (56%)
 create mode 100644 viewers/graphql/adoc/modules/ROOT/pages/rich-value-semantics.adoc
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/{RichPropertyGetClobName.java => RichPropertyGetBlobByteLength.java} (71%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/{RichPropertyGetClobName.java => RichPropertyGetClobByteLength.java} (71%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/{RichPropertyGetClobName.java => RichPropertyGetClobCharacterLength.java} (73%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/{common/query/meta/CommonMetaIcon.java => rich/query/RichResourceMetadataField.java} (53%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerBigInteger.java => ScalarMarshallerApplicationFeatureId.java} (63%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerBooleanPrimitive.java => ScalarMarshallerBookmark.java} (67%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerByteWrapper.java => ScalarMarshallerJavaSqlDate.java} (65%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerByteWrapper.java => ScalarMarshallerJavaSqlTime.java} (72%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerBooleanPrimitive.java => ScalarMarshallerJavaSqlTimestamp.java} (64%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerFloatWrapper.java => ScalarMarshallerJavaUtilDate.java} (62%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerJdk8ZonedDateTime.java => ScalarMarshallerJdk8LocalDateTime.java} (62%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerString.java => ScalarMarshallerLocale.java} (70%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerString.java => ScalarMarshallerMarkup.java} (72%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerBigInteger.java => ScalarMarshallerPassword.java} (68%)
 copy viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/{ScalarMarshallerBooleanPrimitive.java => ScalarMarshallerUrl.java} (61%)
 create mode 100644 viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/BuiltInValueTypeClassifications.java
 create mode 100644 viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalars.java
 create mode 100644 viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ResourceValueTypes.java
 create mode 100644 viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalarsTest.java
 create mode 100644 viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/ScalarMarshallerInputCapabilityTest.java
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumber.java
 copy commons/src/test/java/org/apache/causeway/commons/functional/ThrowingRunnableTest.java => viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberTest.java (53%)
 copy extensions/security/secman/persistence-jpa/src/main/java/org/apache/causeway/extensions/secman/jpa/util/RegexReplacer.java => viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberValueSemantics.java (56%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ScalarMarshallerComplexNumber.java
 copy regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/bad/InvalidContradictingTypeSemantics.java => viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValue.java (77%)
 copy valuetypes/asciidoc/metamodel/src/main/java/org/apache/causeway/valuetypes/asciidoc/metamodel/semantics/ChangesDtoValueSemanticsPrettyRender.java => viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValueSemantics.java (51%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar.approved.json
 copy viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/{Calculator_IntegTest.each.add_double_wrappers._.gql => Calculator_IntegTest.each.malformed_value_is_rejected._.gql} (58%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected.approved.json
 copy viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/{Calculator_IntegTest.each.add_double_wrappers._.gql => Calculator_IntegTest.each.unsupported_value_input_is_rejected._.gql} (58%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/BuiltInValueTypeClassifications_IntegTest.java
 copy viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/{calc/EditableMementoViewModel_IntegTest.java => special/LegacyUnsupportedOutputPolicy_IntegTest.java} (55%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourceValuePolicy_IntegTest.java
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent._.gql
 copy viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/{univ/staff/Staff_IntegTest.each.staff_member_name_edit_invalid.approved.json => special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent.approved.json} (64%)
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources.approved.json
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource._.gql
 create mode 100644 viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource.approved.json


(causeway) 03/04: adds rich GraphQL value and resource semantics

Posted by da...@apache.org.
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 3ade9dc479a293688c844134c8a2064d781cd650
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Aug 18 12:18:06 2026 +0100

    adds rich GraphQL value and resource semantics
---
 .../pages/sections/causeway.viewer.graphql.adoc    |  27 +
 .../core/config/CausewayConfiguration.java         |  49 +-
 .../tasks.md                                       |  12 +-
 .../modules/ROOT/pages/rich-value-semantics.adoc   | 199 +++++
 .../adoc/modules/ROOT/pages/spi-services.adoc      |   8 +-
 .../adoc/modules/ROOT/partials/module-nav.adoc     |   1 +
 .../applib/marshallers/ScalarMarshaller.java       |  11 +
 .../marshallers/ScalarMarshallerAbstract.java      |  36 +-
 .../domain/common/query/ObjectFeatureUtils.java    |   3 +
 .../rich/mutation/RichMutationForProperty.java     |  18 +-
 .../domain/rich/query/RichActionParamsParam.java   |  36 +
 .../domain/rich/query/RichPropertyGetBlob.java     |  30 +
 .../rich/query/RichPropertyGetBlobAbstract.java    |  14 +-
 .../rich/query/RichPropertyGetBlobByteLength.java  |  41 +
 .../domain/rich/query/RichPropertyGetClob.java     |  34 +
 .../rich/query/RichPropertyGetClobAbstract.java    |  14 +-
 .../rich/query/RichPropertyGetClobByteLength.java  |  41 +
 .../query/RichPropertyGetClobCharacterLength.java  |  40 +
 .../domain/rich/query/RichPropertyValidate.java    |  22 +-
 .../rich/query/RichResourceMetadataField.java      |  51 ++
 ...a => ScalarMarshallerApplicationFeatureId.java} |  17 +-
 .../marshallers/ScalarMarshallerBigDecimal.java    |   2 +-
 .../marshallers/ScalarMarshallerBigInteger.java    |   2 +-
 ...nWrapper.java => ScalarMarshallerBookmark.java} |  17 +-
 .../ScalarMarshallerBooleanPrimitive.java          |   2 +-
 .../ScalarMarshallerBooleanWrapper.java            |   2 +-
 .../marshallers/ScalarMarshallerBytePrimitive.java |   2 +-
 .../marshallers/ScalarMarshallerByteWrapper.java   |   2 +-
 .../marshallers/ScalarMarshallerCharPrimitive.java |   2 +-
 .../ScalarMarshallerCharacterWrapper.java          |   2 +-
 .../ScalarMarshallerDoublePrimitive.java           |   2 +-
 .../marshallers/ScalarMarshallerDoubleWrapper.java |   2 +-
 .../ScalarMarshallerFloatPrimitive.java            |   2 +-
 .../marshallers/ScalarMarshallerFloatWrapper.java  |   2 +-
 .../marshallers/ScalarMarshallerIntPrimitive.java  |   2 +-
 .../ScalarMarshallerIntegerWrapper.java            |   2 +-
 ...apper.java => ScalarMarshallerJavaSqlDate.java} |  19 +-
 ...apper.java => ScalarMarshallerJavaSqlTime.java} |  16 +-
 ....java => ScalarMarshallerJavaSqlTimestamp.java} |  20 +-
 ...pper.java => ScalarMarshallerJavaUtilDate.java} |  24 +-
 .../marshallers/ScalarMarshallerJdk8LocalDate.java |   2 +-
 ...java => ScalarMarshallerJdk8LocalDateTime.java} |  23 +-
 .../marshallers/ScalarMarshallerJdk8LocalTime.java |   2 +-
 .../ScalarMarshallerJdk8OffsetDateTime.java        |   2 +-
 .../ScalarMarshallerJdk8OffsetTime.java            |   2 +-
 .../ScalarMarshallerJdk8ZonedDateTime.java         |   2 +-
 ...llerString.java => ScalarMarshallerLocale.java} |  18 +-
 .../marshallers/ScalarMarshallerLongPrimitive.java |   2 +-
 .../marshallers/ScalarMarshallerLongWrapper.java   |   2 +-
 ...llerString.java => ScalarMarshallerMarkup.java} |  15 +-
 .../model/marshallers/ScalarMarshallerObject.java  |  18 +-
 ...nWrapper.java => ScalarMarshallerPassword.java} |  17 +-
 .../ScalarMarshallerShortPrimitive.java            |   2 +-
 .../marshallers/ScalarMarshallerShortWrapper.java  |   2 +-
 .../model/marshallers/ScalarMarshallerString.java  |   2 +-
 ...ooleanWrapper.java => ScalarMarshallerUrl.java} |  26 +-
 .../model/marshallers/ScalarMarshallerUuid.java    |   2 +-
 .../types/BuiltInValueTypeClassifications.java     | 114 +++
 .../graphql/model/types/GraphQLValueScalars.java   | 292 ++++++
 .../graphql/model/types/ResourceValueTypes.java    | 436 +++++++++
 .../viewer/graphql/model/types/ScalarMapper.java   |   4 +
 .../types/ScalarMapperUsingScalarMarshallers.java  |  17 +
 .../graphql/model/types/TypeMapperDefault.java     |  18 +-
 .../domain/common/query/ResourcePathTest.java      |   4 +
 .../model/types/GraphQLValueScalarsTest.java       | 112 +++
 .../types/ScalarMarshallerInputCapabilityTest.java |  63 ++
 .../viewer/test/domain/calc/Calculator.java        | 140 +++
 .../viewer/test/domain/calc/ComplexNumber.java     |  50 ++
 .../viewer/test/domain/calc/ComplexNumberTest.java |  48 +
 .../domain/calc/ComplexNumberValueSemantics.java}  |  30 +-
 .../domain/calc/ScalarMarshallerComplexNumber.java | 105 +++
 .../viewer/test/domain/calc/UnmappedValue.java}    |  21 +-
 .../test/domain/calc/UnmappedValueSemantics.java   |  52 ++
 .../viewer/test/domain/dept/StaffMember.java       |  18 +-
 ...r_IntegTest.each.built_in_value_semantics._.gql |  32 +
 ...est.each.built_in_value_semantics.approved.json |  51 ++
 ...st.each.introspects_fail_closed_built_ins._.gql |  19 +
 ...introspects_fail_closed_built_ins.approved.json |  89 ++
 ...IntegTest.each.introspects_resource_input._.gql |  19 +
 ...t.each.introspects_resource_input.approved.json |  43 +
 ...egTest.each.introspects_unsupported_input._.gql |   7 +
 ...ach.introspects_unsupported_input.approved.json |   9 +
 ...r_IntegTest.each.introspects_value_scalar._.gql |   7 +
 ...est.each.introspects_value_scalar.approved.json |   9 +
 ...ntegTest.each.malformed_value_is_rejected._.gql |  11 +
 ....each.malformed_value_is_rejected.approved.json |  12 +
 ...Test.each.resource_media_type_is_rejected._.gql |  13 +
 ...h.resource_media_type_is_rejected.approved.json |  24 +
 ...ulator_IntegTest.each.some_locale.approved.json |   2 +-
 ....each.unsupported_value_input_is_rejected._.gql |  11 +
 ...supported_value_input_is_rejected.approved.json |  12 +
 ...gTest.each.value_and_resource_round_trips._.gql | 132 +++
 ...ch.value_and_resource_round_trips.approved.json | 152 ++++
 .../BuiltInValueTypeClassifications_IntegTest.java |  90 ++
 .../LegacyUnsupportedOutputPolicy_IntegTest.java   | 102 +++
 .../test/e2e/special/ResourcePolicy_IntegTest.java |  25 +
 .../e2e/special/ResourceValuePolicy_IntegTest.java | 228 +++++
 ...ValidationDoesNotProcessOrDiscloseContent._.gql |  19 +
 ...onDoesNotProcessOrDiscloseContent.approved.json |  28 +
 .../viewer/test/e2e/special/Staff_2_IntegTest.java |  10 +
 .../staffmutating/StaffMutating_IntegTest.java     |  20 +-
 ...ing_IntegTest.staff_member_edit_resources._.gql |  35 +
 ...gTest.staff_member_edit_resources.approved.json |  31 +
 ....staff_member_rejects_unaccepted_resource._.gql |  12 +
 ...ember_rejects_unaccepted_resource.approved.json |  16 +
 viewers/graphql/test/src/test/resources/schema.gql | 995 ++++++++++++++++++++-
 ...ulator_IntegTest.each.some_locale.approved.json |   2 +-
 107 files changed, 4530 insertions(+), 228 deletions(-)

diff --git a/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc b/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
index bd335eae6d6..fa5d26b2a60 100644
--- a/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
+++ b/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
@@ -100,6 +100,24 @@ external-path-prefix
 | null
 
 
+|
+[[causeway.viewer.graphql.resources.inline-input-max-bytes]]
+causeway.viewer.graphql.resources. +
+inline-input-max-bytes
+
+|  1048576
+| Maximum decoded Blob or UTF-8 Clob input size.
+
+
+|
+[[causeway.viewer.graphql.resources.inline-output-max-bytes]]
+causeway.viewer.graphql.resources. +
+inline-output-max-bytes
+
+|  1048576
+| Maximum inline Blob or UTF-8 Clob action-result size.
+
+
 |
 [[causeway.viewer.graphql.resources.response-type]]
 causeway.viewer.graphql.resources. +
@@ -157,6 +175,15 @@ zoned-date-time-format
 | null
 
 
+|
+[[causeway.viewer.graphql.values.unsupported-output-policy]]
+causeway.viewer.graphql.values. +
+unsupported-output-policy
+
+|  REDACTED
+| Controls output for values without an explicit GraphQL strategy. `REDACTED` returns a constant non-disclosing representation; `LEGACY_STRING` temporarily restores implicit `toString()` output. Input remains unsupported under both policies.
+
+
 |
 [[causeway.viewer.graphql.schema-style]]
 causeway.viewer.graphql. +
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
index d48abecbe71..df385fed104 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
@@ -2384,6 +2384,8 @@ public record Graphql(
             @DefaultValue
             ScalarMarshaller scalarMarshaller,
             @DefaultValue
+            Values values,
+            @DefaultValue
             Resources resources,
             @DefaultValue
             Authentication authentication
@@ -2592,10 +2594,34 @@ public record ScalarMarshaller(
                 String zonedDateTimeFormat) {
             }
 
+            /**
+             * Controls GraphQL behavior for values without an explicit marshaller or structured mapping.
+             */
+            public record Values(
+                    /**
+                     * How an unknown value is represented on output.
+                     * Input remains unsupported under every policy.
+                     */
+                    @DefaultValue("REDACTED")
+                    UnsupportedOutputPolicy unsupportedOutputPolicy) {
+
+                public enum UnsupportedOutputPolicy {
+                    /**
+                     * Return a constant non-disclosing unsupported representation without invoking the value's
+                     * {@code toString()} method.
+                     */
+                    REDACTED,
+                    /**
+                     * Temporarily retain the previous implicit GraphQL String serialization during migration.
+                     */
+                    LEGACY_STRING
+                }
+            }
+
             /**
              * The different ways in which resources ({@link org.apache.causeway.applib.value.Blob} bytes,
-             * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) can be downloaded from the
-             * resource controller.
+             * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) are made available.
+             * For Blob and Clob values this policy also gates bounded GraphQL input and action-result content.
              */
             public enum ResponseType {
                 /**
@@ -2603,7 +2629,8 @@ public enum ResponseType {
                  *
                  * <p>In this case any {@link org.apache.causeway.applib.value.Blob} and
                  * {@link org.apache.causeway.applib.value.Clob} properties will <i>not</i> provide a link to
-                 * the URL.  Attempting to download from the resource controller will result in a 403 (forbidden).
+                 * the URL, resource input is unsupported, and action results remain metadata-only.
+                 * Attempting to download from the resource controller will result in a 403 (forbidden).
                  */
                 FORBIDDEN,
                 /**
@@ -2643,11 +2670,25 @@ public record Resources(
                 ResponseType structuralMetadataResponseType,
                 /**
                  * How {@link org.apache.causeway.applib.value.Blob} bytes and
-                 * {@link org.apache.causeway.applib.value.Clob} characters are returned.
+                 * {@link org.apache.causeway.applib.value.Clob} characters are returned, and whether bounded
+                 * GraphQL resource input and action-result content are enabled.
                  * If unset, temporarily falls back to {@link #responseType()}.
                  */
                 @Nullable
                 ResponseType valueContentResponseType,
+                /**
+                 * Maximum decoded byte size accepted for inline Blob or UTF-8 Clob GraphQL input.
+                 */
+                @Min(value = 1)
+                @DefaultValue("1048576")
+                int inlineInputMaxBytes,
+                /**
+                 * Maximum byte size emitted inline for Blob or UTF-8 Clob action results.
+                 * Larger values remain metadata-only and property content continues to use secured resource links.
+                 */
+                @Min(value = 1)
+                @DefaultValue("1048576")
+                int inlineOutputMaxBytes,
                 /**
                  * Optional externally visible path prefix for a deployment behind a path-rewriting reverse proxy.
                  * The prefix is combined with the servlet context and configured GraphQL endpoint path when links are
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 ee056d3b198..6227f210a18 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
@@ -29,9 +29,9 @@
 
 ## 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.
+- [x] 5.1 Inventory and classify every framework-provided `ValueSemanticsProvider` as reversible, structured, protected, output-only, or unsupported.
+- [x] 5.2 Make `ScalarMarshaller` input support an explicit opt-in while keeping existing implementations binary-safe and output-capable.
+- [x] 5.3 Replace implicit unknown-value `toString()` output with a non-disclosing default and an explicit temporary `LEGACY_STRING` migration policy.
+- [x] 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.
+- [x] 5.5 Add closed-inventory, migration, introspection, round-trip, and non-disclosure tests plus corresponding configuration and user documentation.
+- [x] 5.6 Regenerate the schema and rerun GraphQL model, viewer, reference-derived, documentation, formatting, approval, and strict OpenSpec validation checks.
diff --git a/viewers/graphql/adoc/modules/ROOT/pages/rich-value-semantics.adoc b/viewers/graphql/adoc/modules/ROOT/pages/rich-value-semantics.adoc
new file mode 100644
index 00000000000..3dae203684c
--- /dev/null
+++ b/viewers/graphql/adoc/modules/ROOT/pages/rich-value-semantics.adoc
@@ -0,0 +1,199 @@
+= Rich GraphQL value and resource semantics
+
+The rich GraphQL schema advertises a value as input-capable only when GraphQL coercion can reconstruct its declared Java type.
+Output compatibility alone does not establish input support.
+
+== Canonical standard values
+
+The viewer provides the following reversible mappings in addition to its established scalar mappings:
+
+[cols="2,2,4",options="header"]
+|===
+|Java type
+|GraphQL scalar
+|Canonical representation
+
+|`java.time.LocalDateTime`
+|`LocalDateTime`
+|ISO-8601 local date-time without an offset or zone, preserving fractional seconds.
+
+|`java.net.URL`
+|`Url`
+|An absolute URL and its normalized external form.
+
+|`java.util.Date`
+|`LegacyDateTime`
+|ISO-8601 UTC instant.
+
+|`java.sql.Date`
+|`SqlDate`
+|ISO-8601 date-only value.
+
+|`java.sql.Timestamp`
+|`SqlTimestamp`
+|ISO-8601 local date-time preserving fractional seconds.
+
+|`java.sql.Time`
+|`SqlTime`
+|ISO-8601 time-only value at second precision.
+
+|`java.util.Locale`
+|`Locale`
+|IETF BCP 47 language tag.
+
+|`org.apache.causeway.applib.services.bookmark.Bookmark`
+|`Bookmark`
+|Causeway `logical-type:identifier` bookmark text.
+
+|`org.apache.causeway.applib.services.appfeat.ApplicationFeatureId`
+|`ApplicationFeatureId`
+|URL-safe encoded Causeway application-feature identifier.
+|===
+
+Malformed values fail during GraphQL coercion before the domain member is invoked.
+Error messages identify the required scalar without echoing variable content.
+Null remains available only where the Causeway member is optional.
+
+A client can inspect one scalar at a time using targeted standard introspection:
+
+[source,graphql]
+----
+query LocalDateTimeContract {
+  __type(name: "LocalDateTime") {
+    kind
+    name
+    description
+  }
+}
+----
+
+The existing rich `datatype` field continues to identify the declared Causeway semantics when multiple Java types use related textual representations.
+No separate datatype catalogue is introduced.
+
+== Built-in classification
+
+Every framework-provided Causeway value semantics has a reviewed GraphQL classification.
+The classification coverage fails if a new framework value is introduced without a decision.
+
+[cols="2,4",options="header"]
+|===
+|Classification
+|Built-in treatment
+
+|Reversible scalar
+|Primitive wrappers, strings, numbers, supported temporal values, URL, UUID, Locale, Bookmark, and ApplicationFeatureId.
+
+|Structured
+|Blob, Clob, and LocalResourcePath.
+
+|Protected
+|Password accepts explicit input but always serializes its Causeway suppressed placeholder.
+
+|Output-only
+|Markup returns its HTML through the documented `Markup` scalar and is never advertised as input-capable.
+
+|Unsupported
+|Composite Identifier, schema DTO, image, and tree values remain non-disclosing until dedicated representations are defined.
+|===
+
+== Application value extensions
+
+An application can register a scalar-shaped value by providing a Spring bean implementing `ScalarMarshaller<T>`.
+The bean selects the Java class and supplies a documented `GraphQLScalarType`.
+A marshaller is output-only by default.
+A reversible implementation explicitly returns `true` from `supportsInput()`, or passes `true` to the corresponding `ScalarMarshallerAbstract` constructor, and reconstructs `T` from the value produced by that scalar's coercion.
+Existing compiled application marshallers remain output-capable but no longer imply input support.
+
+Marshaller ordering follows the existing `@Priority` chain, with the first matching marshaller winning.
+A custom scalar must use typed GraphQL coercion exceptions and must not place rejected input values in messages.
+The example `ComplexNumber` marshaller uses conventional algebraic text such as `3+4i` or `3-4i`.
+Structured application values can replace the `TypeMapper` SPI when a scalar is not an appropriate representation.
+The viewer does not infer constructors, parsers, or factories from `toString()`.
+
+When no explicit marshaller exists, output uses `UnsupportedValue` and returns the constant `[unsupported]` without invoking the domain value's `toString()` method.
+Input uses the same documented scalar, which always rejects coercion and directs the application to register `ScalarMarshaller` or `TypeMapper`.
+This replaces the previous behavior that serialized arbitrary output and passed a raw Java `String` to an incompatible domain member.
+Applications requiring a migration interval can explicitly set xref:refguide:config:sections/causeway.viewer.graphql.adoc#causeway.viewer.graphql.values.unsupported-output-policy[`causeway.viewer.graphql.values.unsupported-output-policy=LEGACY_STRING`].
+The legacy policy restores only output serialization; input remains unsupported.
+
+== Local resource paths
+
+`LocalResourcePath` uses structured input and output so its path and browser-opening strategy both survive a round trip:
+
+[source,graphql]
+----
+input LocalResourcePathInput {
+  path: String!
+  openUrlStrategy: OpenUrlStrategy!
+}
+
+type LocalResourcePathValue {
+  path: String!
+  openUrlStrategy: OpenUrlStrategy!
+}
+----
+
+The `OpenUrlStrategy` enum contains `NEW_WINDOW` and `SAME_WINDOW`.
+This value is separate from Blob and Clob content policy because it identifies an application-local navigation target rather than transferring resource bytes.
+
+== Blob and Clob input
+
+When value-content policy is enabled, Blob input uses this generated shape:
+
+[source,graphql]
+----
+input BlobInput {
+  name: String!
+  mimeType: String!
+  base64: String!
+}
+----
+
+Clob input uses this generated shape:
+
+[source,graphql]
+----
+input ClobInput {
+  name: String!
+  mimeType: String!
+  chars: String!
+}
+----
+
+Blob content is RFC 4648 base64 and Clob limits are measured using its UTF-8 encoded byte length.
+The viewer validates the configured limit before constructing the Causeway `Blob` or `Clob`.
+It also enforces a member's `fileAccept` constraint using exact media types, wildcard media groups, or filename extensions.
+Normal Causeway visibility, usability, and validity checks still run before property update or action invocation.
+
+The default decoded input limit is one mebibyte.
+Configure it with xref:refguide:config:sections/causeway.viewer.graphql.adoc#causeway.viewer.graphql.resources.inline-input-max-bytes[`causeway.viewer.graphql.resources.inline-input-max-bytes`].
+When value-content policy is `FORBIDDEN`, resource input resolves to `UnsupportedValue` and resource parameter metadata reports `FORBIDDEN`.
+Both `DIRECT` and `ATTACHMENT` enable bounded GraphQL resource values; attachment disposition continues to apply only to resource-controller responses.
+
+== Resource output and metadata
+
+Blob and Clob properties retain their secured, same-origin resource links from the resource-link safety contract.
+Their metadata now also reports byte length, member `fileAccept`, input limit, and transfer mode without transferring content.
+Clob metadata additionally reports character length.
+
+Blob and Clob action results use `BlobValue` and `ClobValue` output objects.
+These expose name, media type, byte length, transfer mode, and the configured inline-output limit.
+Content is included only when value-content policy is enabled and the value is within that limit.
+An oversized action result reports `METADATA_ONLY` and returns null for its base64 or character-content field.
+
+The default inline-output limit is one mebibyte.
+Configure it with xref:refguide:config:sections/causeway.viewer.graphql.adoc#causeway.viewer.graphql.resources.inline-output-max-bytes[`causeway.viewer.graphql.resources.inline-output-max-bytes`].
+Large property values continue to use the authorization-rechecking resource controller rather than inline GraphQL output.
+
+== Security and migration
+
+Passwords can be submitted through the protected `Password` scalar but retain only their Causeway suppressed placeholder on every output and never expose their underlying value.
+Hidden member values and resource capabilities remain unavailable, and negotiation or invocation attempts are vetoed before submitted content is processed.
+Opaque, tree-shaped, composite, or custom values without an explicit strategy return only `[unsupported]` and reject input through `UnsupportedValue`.
+Resource validation, coercion errors, and diagnostics are bounded and do not include submitted content.
+
+Existing clients reading implicit fallback strings must either register an explicit safe output marshaller or temporarily select `LEGACY_STRING`.
+Existing custom marshallers that require input must explicitly opt in after verifying reversible coercion.
+Clients that previously submitted arbitrary strings for unsupported inputs must register a reversible strategy or stop advertising that operation as input-capable.
+Clients submitting Blob or Clob arguments must migrate from descriptive strings to `BlobInput` or `ClobInput`.
+The category-specific resource policies and legacy global fallback are documented in xref:gqlv:ROOT:about.adoc[GraphQL API].
diff --git a/viewers/graphql/adoc/modules/ROOT/pages/spi-services.adoc b/viewers/graphql/adoc/modules/ROOT/pages/spi-services.adoc
index 2008105552f..e8a77916a3d 100644
--- a/viewers/graphql/adoc/modules/ROOT/pages/spi-services.adoc
+++ b/viewers/graphql/adoc/modules/ROOT/pages/spi-services.adoc
@@ -47,6 +47,12 @@ This configuration property define the format of such a string for a zoned date
 The default for a zoned date time is "yyyy-MM-dd HH:mm:ss z".
 
 The default implementations can be overridden if necessary by providing an equivalent implementation with an earlier `@Priority`.
-Additional scalar datatypes can be supporting by providing additional implementations.
+Additional scalar datatypes can be supported by providing additional implementations.
+A scalar marshaller is output-only by default.
+A reversible implementation must explicitly return `true` from `supportsInput()`, or pass `true` to the corresponding `ScalarMarshallerAbstract` constructor, and must reconstruct its declared Java type deterministically.
+Existing compiled marshallers remain output-capable but do not implicitly gain input capability.
+Generated input for an output-only marshaller uses the rejecting `UnsupportedValue` scalar rather than passing through a raw string.
+Values without any explicit marshaller are redacted on output by default; the temporary `LEGACY_STRING` policy is available for migration only.
+See xref:gqlv:ROOT:rich-value-semantics.adoc[] for canonical formats, custom registration, and structured resource values.
 
 
diff --git a/viewers/graphql/adoc/modules/ROOT/partials/module-nav.adoc b/viewers/graphql/adoc/modules/ROOT/partials/module-nav.adoc
index f8d2eac3b41..226ed896802 100644
--- a/viewers/graphql/adoc/modules/ROOT/partials/module-nav.adoc
+++ b/viewers/graphql/adoc/modules/ROOT/partials/module-nav.adoc
@@ -2,6 +2,7 @@
 * xref:gqlv:ROOT:setup-and-configuration.adoc[]
 * xref:gqlv:ROOT:reference-application-coverage.adoc[]
 * xref:gqlv:ROOT:rich-object-interactions.adoc[]
+* xref:gqlv:ROOT:rich-value-semantics.adoc[]
 * xref:gqlv:ROOT:testsupport.adoc[]
 * xref:gqlv:ROOT:spi-services.adoc[]
 * xref:gqlv:ROOT:i18n.adoc[]
diff --git a/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshaller.java b/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshaller.java
index fc627e048da..c1a29a64311 100644
--- a/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshaller.java
+++ b/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshaller.java
@@ -51,6 +51,17 @@ public interface ScalarMarshaller<K> {
      */
     GraphQLScalarType getGqlScalarType();
 
+    /**
+     * Whether this marshaller provides reversible GraphQL input coercion.
+     *
+     * <p>The default is {@code false}: registering output coercion never implicitly grants input
+     * capability. Reversible marshallers must opt in explicitly so unsupported inputs are rejected
+     * before domain invocation.</p>
+     */
+    default boolean supportsInput() {
+        return false;
+    }
+
     /**
      * Unmarshal the provided graphQL value into its Java equivalent.
      *
diff --git a/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshallerAbstract.java b/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshallerAbstract.java
index 9410407cbcc..568f39e64f6 100644
--- a/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshallerAbstract.java
+++ b/viewers/graphql/applib/src/main/java/org/apache/causeway/viewer/graphql/applib/marshallers/ScalarMarshallerAbstract.java
@@ -23,14 +23,12 @@
 import org.apache.causeway.core.config.CausewayConfiguration;
 
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 
 /**
  * Convenience adapter for {@link ScalarMarshaller} SPI.
  *
  * @param <K>
  */
-@RequiredArgsConstructor
 public abstract class ScalarMarshallerAbstract<K> implements ScalarMarshaller<K> {
 
     final Class<? extends K> javaClass;
@@ -40,8 +38,40 @@ public abstract class ScalarMarshallerAbstract<K> implements ScalarMarshaller<K>
 
     protected final CausewayConfiguration causewayConfiguration;
 
-    public boolean handles(Class<?> javaClass) {
+    private final boolean supportsInput;
+
+    /**
+     * Compatibility constructor for output-only application marshallers.
+     */
+    protected ScalarMarshallerAbstract(
+            final Class<? extends K> javaClass,
+            final GraphQLScalarType gqlScalarType,
+            final CausewayConfiguration causewayConfiguration) {
+        this(javaClass, gqlScalarType, causewayConfiguration, false);
+    }
+
+    /**
+     * Constructor for marshallers that explicitly declare their input capability.
+     */
+    protected ScalarMarshallerAbstract(
+            final Class<? extends K> javaClass,
+            final GraphQLScalarType gqlScalarType,
+            final CausewayConfiguration causewayConfiguration,
+            final boolean supportsInput) {
+        this.javaClass = javaClass;
+        this.gqlScalarType = gqlScalarType;
+        this.causewayConfiguration = causewayConfiguration;
+        this.supportsInput = supportsInput;
+    }
+
+    @Override
+    public boolean handles(final Class<?> javaClass) {
         return this.javaClass.isAssignableFrom(javaClass);
     }
 
+    @Override
+    public boolean supportsInput() {
+        return supportsInput;
+    }
+
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
index ad4f2e8973b..1d635cc1b3e 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
@@ -31,6 +31,7 @@
 import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.Environment;
 import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 
 import lombok.experimental.UtilityClass;
 
@@ -157,11 +158,13 @@ private static ManagedObject adaptArgument(
             }
             var convertedValues = values.stream()
                     .map(value -> unmarshalValue(elementType, value, environment, context))
+                    .peek(value -> ResourceValueTypes.validateFileAccept(parameter, value))
                     .toList();
             return ManagedObject.adaptParameter(parameter, convertedValues);
         }
 
         var convertedValue = unmarshalValue(elementType, argumentValue, environment, context);
+        ResourceValueTypes.validateFileAccept(parameter, convertedValue);
         return ManagedObject.adaptParameter(parameter, convertedValue);
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichMutationForProperty.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichMutationForProperty.java
index a5c99b2a3df..6e609c619ab 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichMutationForProperty.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichMutationForProperty.java
@@ -39,6 +39,7 @@
 import org.apache.causeway.viewer.graphql.model.exceptions.DisabledException;
 import org.apache.causeway.viewer.graphql.model.exceptions.HiddenException;
 import org.apache.causeway.viewer.graphql.model.exceptions.InvalidException;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 import org.apache.causeway.viewer.graphql.model.types.TypeMapper;
 
 //@Slf4j
@@ -84,14 +85,6 @@ protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment
         var sourcePojo = ObjectFeatureUtils.requirePojo(objectSpec, target, environment, context);
         var managedObject = ManagedObject.adaptSingular(objectSpec, sourcePojo);
 
-        var argumentValue = dataFetchingEnvironment.getArgument(oneToOneAssociation.asciiId());
-        var argumentPojo = ObjectFeatureUtils.unmarshalValue(
-                oneToOneAssociation.getElementType(),
-                argumentValue,
-                environment,
-                context);
-        var argumentManagedObject = ManagedObject.adaptProperty(oneToOneAssociation, argumentPojo);
-
         var visibleConsent = oneToOneAssociation.isVisible(managedObject, InteractionInitiatedBy.USER, Where.ANYWHERE);
         if (visibleConsent.isVetoed()) {
             throw new HiddenException(oneToOneAssociation.getFeatureIdentifier());
@@ -102,6 +95,15 @@ protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment
             throw new DisabledException(oneToOneAssociation.getFeatureIdentifier());
         }
 
+        var argumentValue = dataFetchingEnvironment.getArgument(oneToOneAssociation.asciiId());
+        var argumentPojo = ObjectFeatureUtils.unmarshalValue(
+                oneToOneAssociation.getElementType(),
+                argumentValue,
+                environment,
+                context);
+        ResourceValueTypes.validateFileAccept(oneToOneAssociation, argumentPojo);
+        var argumentManagedObject = ManagedObject.adaptProperty(oneToOneAssociation, argumentPojo);
+
         var validityConsent = oneToOneAssociation.isAssociationValid(managedObject, argumentManagedObject, InteractionInitiatedBy.USER);
         if (validityConsent.isVetoed()) {
             throw new InvalidException(validityConsent);
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichActionParamsParam.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichActionParamsParam.java
index 8c8b2edc0c6..670ddcdc81b 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichActionParamsParam.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichActionParamsParam.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.viewer.graphql.model.domain.rich.query;
 
+import graphql.Scalars;
 import graphql.schema.DataFetchingEnvironment;
 import graphql.schema.GraphQLFieldDefinition;
 
@@ -36,6 +37,7 @@
 import org.apache.causeway.viewer.graphql.model.domain.common.interactors.ActionParamInteractor;
 import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
 import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectActionParameterProvider;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 import org.apache.causeway.viewer.graphql.model.types.TypeMapper;
 
 import lombok.Getter;
@@ -67,6 +69,9 @@ public class RichActionParamsParam
     private final RichActionParamsParamDefault default_;
     private final RichActionParamsParamValidate validate;
     private final RichActionParamsParamDatatype datatype;
+    private final RichResourceMetadataField resourceFileAccept;
+    private final RichResourceMetadataField resourceInputMaxBytes;
+    private final RichResourceMetadataField resourceInputMode;
 
     public RichActionParamsParam(
             final ActionInteractor holder,
@@ -86,6 +91,9 @@ public RichActionParamsParam(
             this.default_ = null;
             this.validate = null;
             this.datatype = null;
+            this.resourceFileAccept = null;
+            this.resourceInputMaxBytes = null;
+            this.resourceInputMode = null;
 
             // nothing else to be done
             return;
@@ -99,6 +107,29 @@ public RichActionParamsParam(
         addChildFieldFor(this.validate = new RichActionParamsParamValidate(this, context));
         addChildFieldFor(this.datatype = new RichActionParamsParamDatatype(this, context));
 
+        var resourceParameter = ResourceValueTypes.isResourceType(oap.getElementType().getCorrespondingClass());
+        addChildFieldFor(this.resourceFileAccept = resourceParameter
+                ? new RichResourceMetadataField(
+                        context,
+                        "fileAccept",
+                        Scalars.GraphQLString,
+                        () -> ResourceValueTypes.fileAccept(oap).orElse(null))
+                : null);
+        addChildFieldFor(this.resourceInputMaxBytes = resourceParameter
+                ? new RichResourceMetadataField(
+                        context,
+                        "inlineInputMaxBytes",
+                        Scalars.GraphQLInt,
+                        () -> context.causewayConfiguration.viewer().graphql().resources().inlineInputMaxBytes())
+                : null);
+        addChildFieldFor(this.resourceInputMode = resourceParameter
+                ? new RichResourceMetadataField(
+                        context,
+                        "resourceInputMode",
+                        Scalars.GraphQLString,
+                        () -> ResourceValueTypes.inputMode(context))
+                : null);
+
         buildObjectTypeAndField(oap.asciiId(), oap.getCanonicalDescription().orElse(oap.getCanonicalFriendlyName()));
     }
 
@@ -137,6 +168,11 @@ protected void addDataFetchersForChildren() {
         validate.addDataFetcher(this);
 
         datatype.addDataFetcher(this);
+        if (resourceFileAccept != null) {
+            resourceFileAccept.addDataFetcher(this);
+            resourceInputMaxBytes.addDataFetcher(this);
+            resourceInputMode.addDataFetcher(this);
+        }
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlob.java
index 7cbfb57ff19..d0d694dc40e 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlob.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlob.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.viewer.graphql.model.domain.rich.query;
 
+import graphql.Scalars;
 import graphql.schema.DataFetchingEnvironment;
 
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
@@ -29,6 +30,7 @@
 import org.apache.causeway.viewer.graphql.model.domain.TypeNames;
 import org.apache.causeway.viewer.graphql.model.domain.common.interactors.MemberInteractor;
 import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 
 public class RichPropertyGetBlob
         extends ElementCustom {
@@ -37,6 +39,10 @@ public class RichPropertyGetBlob
 
     final RichPropertyGetBlobName blobName;
     final RichPropertyGetBlobMimeType blobMimeType;
+    final RichPropertyGetBlobByteLength blobByteLength;
+    final RichResourceMetadataField fileAccept;
+    final RichResourceMetadataField inputMaxBytes;
+    final RichResourceMetadataField transferMode;
     final RichPropertyGetBlobBytes blobBytes;
 
     private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration;
@@ -53,12 +59,32 @@ public RichPropertyGetBlob(
             // type already exists, nothing else to do.
             this.blobName = null;
             this.blobMimeType = null;
+            this.blobByteLength = null;
+            this.fileAccept = null;
+            this.inputMaxBytes = null;
+            this.transferMode = null;
             this.blobBytes = null;
             return;
         }
 
         addChildFieldFor(blobName = new RichPropertyGetBlobName(memberInteractor, context));
         addChildFieldFor(blobMimeType = new RichPropertyGetBlobMimeType(memberInteractor, context));
+        addChildFieldFor(blobByteLength = new RichPropertyGetBlobByteLength(memberInteractor, context));
+        addChildFieldFor(fileAccept = new RichResourceMetadataField(
+                context,
+                "fileAccept",
+                Scalars.GraphQLString,
+                () -> ResourceValueTypes.fileAccept(memberInteractor.getObjectMember()).orElse(null)));
+        addChildFieldFor(inputMaxBytes = new RichResourceMetadataField(
+                context,
+                "inlineInputMaxBytes",
+                Scalars.GraphQLInt,
+                () -> graphqlConfiguration.resources().inlineInputMaxBytes()));
+        addChildFieldFor(transferMode = new RichResourceMetadataField(
+                context,
+                "transferMode",
+                Scalars.GraphQLString,
+                () -> isValueContentEnabled() ? "RESOURCE_LINK" : "METADATA_ONLY"));
         addChildFieldFor(blobBytes = isValueContentEnabled() ? new RichPropertyGetBlobBytes(memberInteractor, context) : null);
 
         setField(newFieldDefinition()
@@ -84,6 +110,10 @@ protected void addDataFetchersForChildren() {
         }
         blobName.addDataFetcher(this);
         blobMimeType.addDataFetcher(this);
+        blobByteLength.addDataFetcher(this);
+        fileAccept.addDataFetcher(this);
+        inputMaxBytes.addDataFetcher(this);
+        transferMode.addDataFetcher(this);
         if (blobBytes != null) {
             blobBytes.addDataFetcher(this);
         }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobAbstract.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobAbstract.java
index 7717ca641ff..a5a018129de 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobAbstract.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobAbstract.java
@@ -24,6 +24,7 @@
 import graphql.Scalars;
 import graphql.schema.DataFetchingEnvironment;
 import graphql.schema.GraphQLFieldDefinition;
+import graphql.schema.GraphQLOutputType;
 
 import org.apache.causeway.applib.value.Blob;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -39,13 +40,22 @@ public abstract class RichPropertyGetBlobAbstract extends Element {
 
     public RichPropertyGetBlobAbstract(
             final MemberInteractor<OneToOneAssociation> memberInteractor,
-            final Context context, String name) {
+            final Context context,
+            final String name) {
+        this(memberInteractor, context, name, Scalars.GraphQLString);
+    }
+
+    protected RichPropertyGetBlobAbstract(
+            final MemberInteractor<OneToOneAssociation> memberInteractor,
+            final Context context,
+            final String name,
+            final GraphQLOutputType outputType) {
         super(context);
         this.memberInteractor = memberInteractor;
 
         setField(GraphQLFieldDefinition.newFieldDefinition()
                     .name(name)
-                    .type(Scalars.GraphQLString)
+                    .type(outputType)
                     .build());
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobByteLength.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobByteLength.java
new file mode 100644
index 00000000000..1ef67e9245c
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetBlobByteLength.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.domain.rich.query;
+
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.viewer.graphql.model.context.Context;
+import org.apache.causeway.viewer.graphql.model.domain.common.interactors.MemberInteractor;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
+
+import graphql.Scalars;
+import graphql.schema.DataFetchingEnvironment;
+
+public class RichPropertyGetBlobByteLength extends RichPropertyGetBlobAbstract {
+
+    public RichPropertyGetBlobByteLength(
+            final MemberInteractor<OneToOneAssociation> memberInteractor,
+            final Context context) {
+        super(memberInteractor, context, "byteLength", Scalars.GraphQLInt);
+    }
+
+    @Override
+    protected Object fetchData(final DataFetchingEnvironment environment) {
+        return fetchDataFromBlob(environment, ResourceValueTypes::byteLength);
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClob.java
index 35a44727da5..24803c75f64 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClob.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClob.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.viewer.graphql.model.domain.rich.query;
 
+import graphql.Scalars;
 import graphql.schema.DataFetchingEnvironment;
 
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
@@ -29,6 +30,7 @@
 import org.apache.causeway.viewer.graphql.model.domain.TypeNames;
 import org.apache.causeway.viewer.graphql.model.domain.common.interactors.MemberInteractor;
 import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 
 public class RichPropertyGetClob
         extends ElementCustom {
@@ -36,6 +38,11 @@ public class RichPropertyGetClob
     final MemberInteractor<OneToOneAssociation> memberInteractor;
     final RichPropertyGetClobName clobName;
     final RichPropertyGetClobMimeType clobMimeType;
+    final RichPropertyGetClobByteLength clobByteLength;
+    final RichPropertyGetClobCharacterLength clobCharacterLength;
+    final RichResourceMetadataField fileAccept;
+    final RichResourceMetadataField inputMaxBytes;
+    final RichResourceMetadataField transferMode;
     final RichPropertyGetClobChars clobChars;
 
     private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration;
@@ -52,12 +59,34 @@ public RichPropertyGetClob(
             // type already exists, nothing else to do.
             this.clobName = null;
             this.clobMimeType = null;
+            this.clobByteLength = null;
+            this.clobCharacterLength = null;
+            this.fileAccept = null;
+            this.inputMaxBytes = null;
+            this.transferMode = null;
             this.clobChars = null;
             return;
         }
 
         addChildFieldFor(clobName = new RichPropertyGetClobName(memberInteractor, context));
         addChildFieldFor(clobMimeType = new RichPropertyGetClobMimeType(memberInteractor, context));
+        addChildFieldFor(clobByteLength = new RichPropertyGetClobByteLength(memberInteractor, context));
+        addChildFieldFor(clobCharacterLength = new RichPropertyGetClobCharacterLength(memberInteractor, context));
+        addChildFieldFor(fileAccept = new RichResourceMetadataField(
+                context,
+                "fileAccept",
+                Scalars.GraphQLString,
+                () -> ResourceValueTypes.fileAccept(memberInteractor.getObjectMember()).orElse(null)));
+        addChildFieldFor(inputMaxBytes = new RichResourceMetadataField(
+                context,
+                "inlineInputMaxBytes",
+                Scalars.GraphQLInt,
+                () -> graphqlConfiguration.resources().inlineInputMaxBytes()));
+        addChildFieldFor(transferMode = new RichResourceMetadataField(
+                context,
+                "transferMode",
+                Scalars.GraphQLString,
+                () -> isValueContentEnabled() ? "RESOURCE_LINK" : "METADATA_ONLY"));
         addChildFieldFor(clobChars = isValueContentEnabled() ? new RichPropertyGetClobChars(memberInteractor, context) : null);
 
         setField(newFieldDefinition()
@@ -83,6 +112,11 @@ protected void addDataFetchersForChildren() {
         }
         clobName.addDataFetcher(this);
         clobMimeType.addDataFetcher(this);
+        clobByteLength.addDataFetcher(this);
+        clobCharacterLength.addDataFetcher(this);
+        fileAccept.addDataFetcher(this);
+        inputMaxBytes.addDataFetcher(this);
+        transferMode.addDataFetcher(this);
         if(clobChars != null) {
             clobChars.addDataFetcher(this);
         }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobAbstract.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobAbstract.java
index 53202def866..4e2b76ea5c7 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobAbstract.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobAbstract.java
@@ -24,6 +24,7 @@
 import graphql.Scalars;
 import graphql.schema.DataFetchingEnvironment;
 import graphql.schema.GraphQLFieldDefinition;
+import graphql.schema.GraphQLOutputType;
 
 import org.apache.causeway.applib.value.Clob;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -39,13 +40,22 @@ public abstract class RichPropertyGetClobAbstract extends Element {
 
     public RichPropertyGetClobAbstract(
             final MemberInteractor<OneToOneAssociation> memberInteractor,
-            final Context context, String name) {
+            final Context context,
+            final String name) {
+        this(memberInteractor, context, name, Scalars.GraphQLString);
+    }
+
+    protected RichPropertyGetClobAbstract(
+            final MemberInteractor<OneToOneAssociation> memberInteractor,
+            final Context context,
+            final String name,
+            final GraphQLOutputType outputType) {
         super(context);
         this.holder = memberInteractor;
 
         setField(GraphQLFieldDefinition.newFieldDefinition()
                     .name(name)
-                    .type(Scalars.GraphQLString)
+                    .type(outputType)
                     .build());
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobByteLength.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobByteLength.java
new file mode 100644
index 00000000000..167b9c98c94
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobByteLength.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.domain.rich.query;
+
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.viewer.graphql.model.context.Context;
+import org.apache.causeway.viewer.graphql.model.domain.common.interactors.MemberInteractor;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
+
+import graphql.Scalars;
+import graphql.schema.DataFetchingEnvironment;
+
+public class RichPropertyGetClobByteLength extends RichPropertyGetClobAbstract {
+
+    public RichPropertyGetClobByteLength(
+            final MemberInteractor<OneToOneAssociation> memberInteractor,
+            final Context context) {
+        super(memberInteractor, context, "byteLength", Scalars.GraphQLInt);
+    }
+
+    @Override
+    protected Object fetchData(final DataFetchingEnvironment environment) {
+        return fetchDataFromClob(environment, ResourceValueTypes::byteLength);
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobCharacterLength.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobCharacterLength.java
new file mode 100644
index 00000000000..a2dfb568e62
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyGetClobCharacterLength.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.domain.rich.query;
+
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.viewer.graphql.model.context.Context;
+import org.apache.causeway.viewer.graphql.model.domain.common.interactors.MemberInteractor;
+
+import graphql.Scalars;
+import graphql.schema.DataFetchingEnvironment;
+
+public class RichPropertyGetClobCharacterLength extends RichPropertyGetClobAbstract {
+
+    public RichPropertyGetClobCharacterLength(
+            final MemberInteractor<OneToOneAssociation> memberInteractor,
+            final Context context) {
+        super(memberInteractor, context, "characterLength", Scalars.GraphQLInt);
+    }
+
+    @Override
+    protected Object fetchData(final DataFetchingEnvironment environment) {
+        return fetchDataFromClob(environment, clob -> clob.chars().length());
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyValidate.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyValidate.java
index 7d30975e275..f57b03f1982 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyValidate.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichPropertyValidate.java
@@ -23,12 +23,18 @@
 
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 
+import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.Element;
+import org.apache.causeway.viewer.graphql.model.domain.Environment;
 import org.apache.causeway.viewer.graphql.model.domain.common.interactors.PropertyInteractor;
+import org.apache.causeway.viewer.graphql.model.domain.common.query.ObjectFeatureUtils;
+import org.apache.causeway.viewer.graphql.model.exceptions.DisabledException;
+import org.apache.causeway.viewer.graphql.model.exceptions.HiddenException;
 import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
+import org.apache.causeway.viewer.graphql.model.types.ResourceValueTypes;
 import org.apache.causeway.viewer.graphql.model.types.TypeMapper;
 
 public class RichPropertyValidate extends Element {
@@ -62,8 +68,22 @@ protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment
         var otoa = holder.getObjectMember();
         var managedObject = ManagedObject.adaptSingular(objectSpecification, sourcePojo);
 
+        var visibleConsent = otoa.isVisible(managedObject, InteractionInitiatedBy.USER, Where.ANYWHERE);
+        if (visibleConsent.isVetoed()) {
+            throw new HiddenException(otoa.getFeatureIdentifier());
+        }
+        var usableConsent = otoa.isUsable(managedObject, InteractionInitiatedBy.USER, Where.ANYWHERE);
+        if (usableConsent.isVetoed()) {
+            throw new DisabledException(otoa.getFeatureIdentifier());
+        }
+
         var arguments = dataFetchingEnvironment.getArguments();
-        var argumentValue = arguments.get(otoa.asciiId());
+        var argumentValue = ObjectFeatureUtils.unmarshalValue(
+                otoa.getElementType(),
+                arguments.get(otoa.asciiId()),
+                new Environment.For(dataFetchingEnvironment),
+                context);
+        ResourceValueTypes.validateFileAccept(otoa, argumentValue);
         var argumentManagedObject = ManagedObject.adaptProperty(otoa, argumentValue);
 
         var valid = otoa.isAssociationValid(managedObject, argumentManagedObject, InteractionInitiatedBy.USER);
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichResourceMetadataField.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichResourceMetadataField.java
new file mode 100644
index 00000000000..c468ba5ad8d
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/query/RichResourceMetadataField.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.domain.rich.query;
+
+import java.util.function.Supplier;
+
+import org.apache.causeway.viewer.graphql.model.context.Context;
+import org.apache.causeway.viewer.graphql.model.domain.Element;
+
+import graphql.schema.DataFetchingEnvironment;
+import graphql.schema.GraphQLFieldDefinition;
+import graphql.schema.GraphQLOutputType;
+
+class RichResourceMetadataField extends Element {
+
+    private final Supplier<Object> valueSupplier;
+
+    RichResourceMetadataField(
+            final Context context,
+            final String name,
+            final GraphQLOutputType outputType,
+            final Supplier<Object> valueSupplier) {
+        super(context);
+        this.valueSupplier = valueSupplier;
+        setField(GraphQLFieldDefinition.newFieldDefinition()
+                .name(name)
+                .type(outputType)
+                .build());
+    }
+
+    @Override
+    protected Object fetchData(final DataFetchingEnvironment environment) {
+        return valueSupplier.get();
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerApplicationFeatureId.java
similarity index 63%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerApplicationFeatureId.java
index 7f6ae212f8f..07768b27681 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerApplicationFeatureId.java
@@ -24,23 +24,24 @@
 import org.springframework.stereotype.Component;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerBooleanWrapper extends ScalarMarshallerAbstract<Boolean> {
+public class ScalarMarshallerApplicationFeatureId extends ScalarMarshallerAbstract<ApplicationFeatureId> {
 
     @Inject
-    public ScalarMarshallerBooleanWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+    public ScalarMarshallerApplicationFeatureId(final CausewayConfiguration causewayConfiguration) {
+        super(ApplicationFeatureId.class, GraphQLValueScalars.APPLICATION_FEATURE_ID, causewayConfiguration, true);
     }
 
     @Override
-    public Boolean unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (Boolean)graphValue;
+    public ApplicationFeatureId unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof ApplicationFeatureId applicationFeatureId
+                ? applicationFeatureId
+                : ApplicationFeatureId.parseEncoded((String) graphValue);
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigDecimal.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigDecimal.java
index ebe18eec8aa..3a84771f46e 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigDecimal.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigDecimal.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerBigDecimal extends ScalarMarshallerAbstract<BigDeci
 
     @Inject
     public ScalarMarshallerBigDecimal(final CausewayConfiguration causewayConfiguration) {
-        super(BigDecimal.class, Scalars.GraphQLString, causewayConfiguration);
+        super(BigDecimal.class, Scalars.GraphQLString, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigInteger.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigInteger.java
index 7f1e495daa6..287fa0280c3 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigInteger.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBigInteger.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerBigInteger extends ScalarMarshallerAbstract<BigInte
 
     @Inject
     public ScalarMarshallerBigInteger(final CausewayConfiguration causewayConfiguration) {
-        super(BigInteger.class, Scalars.GraphQLString, causewayConfiguration);
+        super(BigInteger.class, Scalars.GraphQLString, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBookmark.java
similarity index 67%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBookmark.java
index 7f6ae212f8f..5eafbfb0cf0 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBookmark.java
@@ -24,23 +24,24 @@
 import org.springframework.stereotype.Component;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerBooleanWrapper extends ScalarMarshallerAbstract<Boolean> {
+public class ScalarMarshallerBookmark extends ScalarMarshallerAbstract<Bookmark> {
 
     @Inject
-    public ScalarMarshallerBooleanWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+    public ScalarMarshallerBookmark(final CausewayConfiguration causewayConfiguration) {
+        super(Bookmark.class, GraphQLValueScalars.BOOKMARK, causewayConfiguration, true);
     }
 
     @Override
-    public Boolean unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (Boolean)graphValue;
+    public Bookmark unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Bookmark bookmark
+                ? bookmark
+                : Bookmark.parseElseFail((String) graphValue);
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java
index 7a9a7cad855..de160b46aa1 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerBooleanPrimitive extends ScalarMarshallerAbstract<B
 
     @Inject
     public ScalarMarshallerBooleanPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+        super(boolean.class, Scalars.GraphQLBoolean, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
index 7f6ae212f8f..f101ac166fd 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerBooleanWrapper extends ScalarMarshallerAbstract<Boo
 
     @Inject
     public ScalarMarshallerBooleanWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBytePrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBytePrimitive.java
index c612eee3d73..6151c5db539 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBytePrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBytePrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerBytePrimitive extends ScalarMarshallerAbstract<Byte
 
     @Inject
     public ScalarMarshallerBytePrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration);
+        super(byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
index b47cfc220af..f9e0608dd66 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerByteWrapper extends ScalarMarshallerAbstract<Byte>
 
     @Inject
     public ScalarMarshallerByteWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration);
+        super(Byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharPrimitive.java
index 57538f11fe0..f339a18f013 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharPrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerCharPrimitive extends ScalarMarshallerAbstract<Char
 
     @Inject
     public ScalarMarshallerCharPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(char.class, Scalars.GraphQLString, causewayConfiguration);
+        super(char.class, Scalars.GraphQLString, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharacterWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharacterWrapper.java
index d3e0efaebf4..64450ce7e4d 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharacterWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerCharacterWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerCharacterWrapper extends ScalarMarshallerAbstract<C
 
     @Inject
     public ScalarMarshallerCharacterWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Character.class, ExtendedScalars.GraphQLChar, causewayConfiguration);
+        super(Character.class, ExtendedScalars.GraphQLChar, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoublePrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoublePrimitive.java
index 84252f860a6..eeb079d621b 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoublePrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoublePrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerDoublePrimitive extends ScalarMarshallerAbstract<Do
 
     @Inject
     public ScalarMarshallerDoublePrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(double.class, Scalars.GraphQLFloat, causewayConfiguration);
+        super(double.class, Scalars.GraphQLFloat, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoubleWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoubleWrapper.java
index 3c75716d3f5..cd1e96af5a9 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoubleWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerDoubleWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerDoubleWrapper extends ScalarMarshallerAbstract<Doub
 
     @Inject
     public ScalarMarshallerDoubleWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Double.class, Scalars.GraphQLFloat, causewayConfiguration);
+        super(Double.class, Scalars.GraphQLFloat, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatPrimitive.java
index 436067e50b5..8222ed41805 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatPrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerFloatPrimitive extends ScalarMarshallerAbstract<Flo
 
     @Inject
     public ScalarMarshallerFloatPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(float.class, Scalars.GraphQLFloat, causewayConfiguration);
+        super(float.class, Scalars.GraphQLFloat, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java
index 3f7319e94ee..bf94b877fde 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerFloatWrapper extends ScalarMarshallerAbstract<Float
 
     @Inject
     public ScalarMarshallerFloatWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Float.class, Scalars.GraphQLFloat, causewayConfiguration);
+        super(Float.class, Scalars.GraphQLFloat, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntPrimitive.java
index c282a807d89..8a15b66c02b 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntPrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerIntPrimitive extends ScalarMarshallerAbstract<Integ
 
     @Inject
     public ScalarMarshallerIntPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(int.class, Scalars.GraphQLInt, causewayConfiguration);
+        super(int.class, Scalars.GraphQLInt, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntegerWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntegerWrapper.java
index 3c813145586..54dcd06b4b0 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntegerWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerIntegerWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerIntegerWrapper extends ScalarMarshallerAbstract<Int
 
     @Inject
     public ScalarMarshallerIntegerWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Integer.class, Scalars.GraphQLInt, causewayConfiguration);
+        super(Integer.class, Scalars.GraphQLInt, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlDate.java
similarity index 65%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlDate.java
index b47cfc220af..e540852c394 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlDate.java
@@ -11,13 +11,15 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.time.LocalDate;
+
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,20 +28,21 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.scalars.ExtendedScalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerByteWrapper extends ScalarMarshallerAbstract<Byte> {
+public class ScalarMarshallerJavaSqlDate extends ScalarMarshallerAbstract<java.sql.Date> {
 
     @Inject
-    public ScalarMarshallerByteWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration);
+    public ScalarMarshallerJavaSqlDate(final CausewayConfiguration causewayConfiguration) {
+        super(java.sql.Date.class, GraphQLValueScalars.SQL_DATE, causewayConfiguration, true);
     }
 
     @Override
-    public Byte unmarshal(final Object graphValue, final Class<?> targetType) {
-        return ((Integer)graphValue).byteValue();
+    public java.sql.Date unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof java.sql.Date date
+                ? date
+                : java.sql.Date.valueOf(LocalDate.parse((String) graphValue));
     }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTime.java
similarity index 72%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTime.java
index b47cfc220af..b5dc3526f8a 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerByteWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTime.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.sql.Time;
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,20 +27,21 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.scalars.ExtendedScalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerByteWrapper extends ScalarMarshallerAbstract<Byte> {
+public class ScalarMarshallerJavaSqlTime extends ScalarMarshallerAbstract<Time> {
 
     @Inject
-    public ScalarMarshallerByteWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Byte.class, ExtendedScalars.GraphQLByte, causewayConfiguration);
+    public ScalarMarshallerJavaSqlTime(final CausewayConfiguration causewayConfiguration) {
+        super(Time.class, GraphQLValueScalars.SQL_TIME, causewayConfiguration, true);
     }
 
     @Override
-    public Byte unmarshal(final Object graphValue, final Class<?> targetType) {
-        return ((Integer)graphValue).byteValue();
+    public Time unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Time time
+                ? time
+                : Time.valueOf((String) graphValue);
     }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTimestamp.java
similarity index 64%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTimestamp.java
index 7a9a7cad855..5d4608f3cfe 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaSqlTimestamp.java
@@ -11,13 +11,16 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,20 +29,21 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerBooleanPrimitive extends ScalarMarshallerAbstract<Boolean> {
+public class ScalarMarshallerJavaSqlTimestamp extends ScalarMarshallerAbstract<Timestamp> {
 
     @Inject
-    public ScalarMarshallerBooleanPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+    public ScalarMarshallerJavaSqlTimestamp(final CausewayConfiguration causewayConfiguration) {
+        super(Timestamp.class, GraphQLValueScalars.SQL_TIMESTAMP, causewayConfiguration, true);
     }
 
     @Override
-    public Boolean unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (Boolean)graphValue;
+    public Timestamp unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Timestamp timestamp
+                ? timestamp
+                : Timestamp.valueOf(LocalDateTime.parse((String) graphValue));
     }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaUtilDate.java
similarity index 62%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaUtilDate.java
index 3f7319e94ee..ff34b97101c 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerFloatWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJavaUtilDate.java
@@ -11,13 +11,16 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.time.Instant;
+import java.util.Date;
+
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,21 +29,26 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerFloatWrapper extends ScalarMarshallerAbstract<Float> {
+public class ScalarMarshallerJavaUtilDate extends ScalarMarshallerAbstract<Date> {
 
     @Inject
-    public ScalarMarshallerFloatWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Float.class, Scalars.GraphQLFloat, causewayConfiguration);
+    public ScalarMarshallerJavaUtilDate(final CausewayConfiguration causewayConfiguration) {
+        super(Date.class, GraphQLValueScalars.LEGACY_DATE_TIME, causewayConfiguration, true);
     }
 
     @Override
-    public Float unmarshal(Object graphValue, Class<?> targetType) {
-        return ((Double) graphValue).floatValue();
+    public boolean handles(final Class<?> javaClass) {
+        return javaClass == Date.class;
     }
 
+    @Override
+    public Date unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Date date
+                ? date
+                : Date.from(Instant.parse((String) graphValue));
+    }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDate.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDate.java
index b3d0d9e8f2b..5d8f70f567a 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDate.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDate.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerJdk8LocalDate extends ScalarMarshallerAbstract<Loca
 
     @Inject
     public ScalarMarshallerJdk8LocalDate(final CausewayConfiguration causewayConfiguration) {
-        super(LocalDate.class, Scalars.GraphQLString, causewayConfiguration);
+        super(LocalDate.class, Scalars.GraphQLString, causewayConfiguration, true);
         scalarMarshallerConfig = causewayConfiguration.viewer().graphql().scalarMarshaller();
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDateTime.java
similarity index 62%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDateTime.java
index 37e2ccb4a5f..53362d10602 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalDateTime.java
@@ -11,14 +11,14 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
-import java.time.ZonedDateTime;
+import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 
 import jakarta.annotation.Priority;
@@ -29,24 +29,21 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerJdk8ZonedDateTime extends ScalarMarshallerAbstract<ZonedDateTime> {
-
-    private final CausewayConfiguration.Viewer.Graphql.ScalarMarshaller scalarMarshallerConfig;
+public class ScalarMarshallerJdk8LocalDateTime extends ScalarMarshallerAbstract<LocalDateTime> {
 
     @Inject
-    public ScalarMarshallerJdk8ZonedDateTime(final CausewayConfiguration causewayConfiguration) {
-        super(ZonedDateTime.class, Scalars.GraphQLString, causewayConfiguration);
-        scalarMarshallerConfig = causewayConfiguration.viewer().graphql().scalarMarshaller();
+    public ScalarMarshallerJdk8LocalDateTime(final CausewayConfiguration causewayConfiguration) {
+        super(LocalDateTime.class, GraphQLValueScalars.LOCAL_DATE_TIME, causewayConfiguration, true);
     }
 
     @Override
-    public ZonedDateTime unmarshal(Object graphValue, Class<?> targetType) {
-        String argumentStr = (String) graphValue;
-        return ZonedDateTime.parse(argumentStr, DateTimeFormatter.ofPattern(scalarMarshallerConfig.zonedDateTimeFormat()));
+    public LocalDateTime unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof LocalDateTime localDateTime
+                ? localDateTime
+                : LocalDateTime.parse((String) graphValue, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
     }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalTime.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalTime.java
index f528a2219a4..766e01a744f 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalTime.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8LocalTime.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerJdk8LocalTime extends ScalarMarshallerAbstract<Loca
 
     @Inject
     public ScalarMarshallerJdk8LocalTime(final CausewayConfiguration causewayConfiguration) {
-        super(LocalTime.class, Scalars.GraphQLString, causewayConfiguration);
+        super(LocalTime.class, Scalars.GraphQLString, causewayConfiguration, true);
         scalarMarshallerConfig = causewayConfiguration.viewer().graphql().scalarMarshaller();
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetDateTime.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetDateTime.java
index 3ef485abf34..bdb2ad91e38 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetDateTime.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetDateTime.java
@@ -37,7 +37,7 @@ public class ScalarMarshallerJdk8OffsetDateTime extends ScalarMarshallerAbstract
 
     @Inject
     public ScalarMarshallerJdk8OffsetDateTime(final CausewayConfiguration causewayConfiguration) {
-        super(OffsetDateTime.class, ExtendedScalars.DateTime, causewayConfiguration);
+        super(OffsetDateTime.class, ExtendedScalars.DateTime, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetTime.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetTime.java
index a070d018489..ca5628d8c2d 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetTime.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8OffsetTime.java
@@ -37,7 +37,7 @@ public class ScalarMarshallerJdk8OffsetTime extends ScalarMarshallerAbstract<Off
 
     @Inject
     public ScalarMarshallerJdk8OffsetTime(final CausewayConfiguration causewayConfiguration) {
-        super(OffsetTime.class, ExtendedScalars.Time, causewayConfiguration);
+        super(OffsetTime.class, ExtendedScalars.Time, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java
index 37e2ccb4a5f..8441d059fb9 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerJdk8ZonedDateTime.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerJdk8ZonedDateTime extends ScalarMarshallerAbstract<
 
     @Inject
     public ScalarMarshallerJdk8ZonedDateTime(final CausewayConfiguration causewayConfiguration) {
-        super(ZonedDateTime.class, Scalars.GraphQLString, causewayConfiguration);
+        super(ZonedDateTime.class, Scalars.GraphQLString, causewayConfiguration, true);
         scalarMarshallerConfig = causewayConfiguration.viewer().graphql().scalarMarshaller();
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLocale.java
similarity index 70%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLocale.java
index d7c8aa6188c..921a1310fac 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLocale.java
@@ -18,6 +18,8 @@
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.util.Locale;
+
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,21 +28,21 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerString extends ScalarMarshallerAbstract<String> {
+public class ScalarMarshallerLocale extends ScalarMarshallerAbstract<Locale> {
 
     @Inject
-    public ScalarMarshallerString(final CausewayConfiguration causewayConfiguration) {
-        super(String.class, Scalars.GraphQLString, causewayConfiguration);
+    public ScalarMarshallerLocale(final CausewayConfiguration causewayConfiguration) {
+        super(Locale.class, GraphQLValueScalars.LOCALE, causewayConfiguration, true);
     }
 
     @Override
-    public String unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (String)graphValue;
+    public Locale unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Locale locale
+                ? locale
+                : new Locale.Builder().setLanguageTag((String) graphValue).build();
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongPrimitive.java
index bd6c0989c08..8a5b1e16a47 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongPrimitive.java
@@ -38,7 +38,7 @@ public class ScalarMarshallerLongPrimitive extends ScalarMarshallerAbstract<Long
 
     @Inject
     public ScalarMarshallerLongPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(long.class, ExtendedScalars.GraphQLLong, causewayConfiguration);
+        super(long.class, ExtendedScalars.GraphQLLong, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongWrapper.java
index 199c924b982..a32b40c9185 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerLongWrapper.java
@@ -38,7 +38,7 @@ public class ScalarMarshallerLongWrapper extends ScalarMarshallerAbstract<Long>
 
     @Inject
     public ScalarMarshallerLongWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Long.class, ExtendedScalars.GraphQLLong, causewayConfiguration);
+        super(Long.class, ExtendedScalars.GraphQLLong, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerMarkup.java
similarity index 72%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerMarkup.java
index d7c8aa6188c..92719e5b5bc 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerMarkup.java
@@ -24,23 +24,22 @@
 import org.springframework.stereotype.Component;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.value.Markup;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerString extends ScalarMarshallerAbstract<String> {
+public class ScalarMarshallerMarkup extends ScalarMarshallerAbstract<Markup> {
 
     @Inject
-    public ScalarMarshallerString(final CausewayConfiguration causewayConfiguration) {
-        super(String.class, Scalars.GraphQLString, causewayConfiguration);
+    public ScalarMarshallerMarkup(final CausewayConfiguration causewayConfiguration) {
+        super(Markup.class, GraphQLValueScalars.MARKUP, causewayConfiguration);
     }
 
     @Override
-    public String unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (String)graphValue;
+    public Markup unmarshal(final Object graphValue, final Class<?> targetType) {
+        throw new IllegalArgumentException("Causeway Markup is output-only in rich GraphQL");
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerObject.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerObject.java
index 65daadb4a9c..bd374182bc5 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerObject.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerObject.java
@@ -26,8 +26,10 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 import graphql.Scalars;
+import graphql.schema.GraphQLScalarType;
 
 /**
  * Acts as a fallback.  We put it last in the list
@@ -39,11 +41,21 @@ public class ScalarMarshallerObject extends ScalarMarshallerAbstract<Object> {
     @Inject
     public ScalarMarshallerObject(
             final CausewayConfiguration causewayConfiguration) {
-        super(Object.class, Scalars.GraphQLString, causewayConfiguration);
+        super(Object.class, outputType(causewayConfiguration), causewayConfiguration);
     }
 
     @Override
-    public Object unmarshal(Object graphValue, Class<?> targetType) {
-        return graphValue;
+    public Object unmarshal(final Object graphValue, final Class<?> targetType) {
+        throw new IllegalArgumentException(
+                "No reversible GraphQL input strategy is registered for " + targetType.getName());
+    }
+
+    private static GraphQLScalarType outputType(final CausewayConfiguration causewayConfiguration) {
+        var values = causewayConfiguration.viewer().graphql().values();
+        return values != null
+                && values.unsupportedOutputPolicy()
+                        == CausewayConfiguration.Viewer.Graphql.Values.UnsupportedOutputPolicy.LEGACY_STRING
+                ? Scalars.GraphQLString
+                : GraphQLValueScalars.UNSUPPORTED_VALUE;
     }
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerPassword.java
similarity index 68%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerPassword.java
index 7f6ae212f8f..cd0c1f016ca 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerPassword.java
@@ -24,23 +24,24 @@
 import org.springframework.stereotype.Component;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.value.Password;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerBooleanWrapper extends ScalarMarshallerAbstract<Boolean> {
+public class ScalarMarshallerPassword extends ScalarMarshallerAbstract<Password> {
 
     @Inject
-    public ScalarMarshallerBooleanWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+    public ScalarMarshallerPassword(final CausewayConfiguration causewayConfiguration) {
+        super(Password.class, GraphQLValueScalars.PASSWORD, causewayConfiguration, true);
     }
 
     @Override
-    public Boolean unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (Boolean)graphValue;
+    public Password unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof Password password
+                ? password
+                : Password.of((String) graphValue);
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortPrimitive.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortPrimitive.java
index f25c4a14320..e73b0cea61f 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortPrimitive.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortPrimitive.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerShortPrimitive extends ScalarMarshallerAbstract<Sho
 
     @Inject
     public ScalarMarshallerShortPrimitive(final CausewayConfiguration causewayConfiguration) {
-        super(short.class, ExtendedScalars.GraphQLShort, causewayConfiguration);
+        super(short.class, ExtendedScalars.GraphQLShort, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortWrapper.java
index 9d28ef8405d..1c44095da36 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerShortWrapper.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerShortWrapper extends ScalarMarshallerAbstract<Short
 
     @Inject
     public ScalarMarshallerShortWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Short.class, ExtendedScalars.GraphQLShort, causewayConfiguration);
+        super(Short.class, ExtendedScalars.GraphQLShort, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
index d7c8aa6188c..f4cfedd7c7a 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerString.java
@@ -35,7 +35,7 @@ public class ScalarMarshallerString extends ScalarMarshallerAbstract<String> {
 
     @Inject
     public ScalarMarshallerString(final CausewayConfiguration causewayConfiguration) {
-        super(String.class, Scalars.GraphQLString, causewayConfiguration);
+        super(String.class, Scalars.GraphQLString, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUrl.java
similarity index 61%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUrl.java
index 7f6ae212f8f..80f470900fa 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerBooleanWrapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUrl.java
@@ -11,13 +11,16 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
 package org.apache.causeway.viewer.graphql.model.marshallers;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 
@@ -26,21 +29,26 @@
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
-
-import graphql.Scalars;
+import org.apache.causeway.viewer.graphql.model.types.GraphQLValueScalars;
 
 @Component
 @Priority(PriorityPrecedence.LATE)
-public class ScalarMarshallerBooleanWrapper extends ScalarMarshallerAbstract<Boolean> {
+public class ScalarMarshallerUrl extends ScalarMarshallerAbstract<URL> {
 
     @Inject
-    public ScalarMarshallerBooleanWrapper(final CausewayConfiguration causewayConfiguration) {
-        super(Boolean.class, Scalars.GraphQLBoolean, causewayConfiguration);
+    public ScalarMarshallerUrl(final CausewayConfiguration causewayConfiguration) {
+        super(URL.class, GraphQLValueScalars.URL_VALUE, causewayConfiguration, true);
     }
 
     @Override
-    public Boolean unmarshal(final Object graphValue, final Class<?> targetType) {
-        return (Boolean)graphValue;
+    public URL unmarshal(final Object graphValue, final Class<?> targetType) {
+        if (graphValue instanceof URL url) {
+            return url;
+        }
+        try {
+            return new URL((String) graphValue);
+        } catch (MalformedURLException ignored) {
+            throw new IllegalArgumentException("Invalid URL value");
+        }
     }
-
 }
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUuid.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUuid.java
index 2153268ffd5..0c903bb3ce7 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUuid.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/marshallers/ScalarMarshallerUuid.java
@@ -40,7 +40,7 @@ public class ScalarMarshallerUuid extends ScalarMarshallerAbstract<UUID> {
 
     @Inject
     public ScalarMarshallerUuid(final CausewayConfiguration causewayConfiguration) {
-        super(UUID.class, ExtendedScalars.UUID, causewayConfiguration);
+        super(UUID.class, ExtendedScalars.UUID, causewayConfiguration, true);
     }
 
     @Override
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/BuiltInValueTypeClassifications.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/BuiltInValueTypeClassifications.java
new file mode 100644
index 00000000000..a52315e1857
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/BuiltInValueTypeClassifications.java
@@ -0,0 +1,114 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.types;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * Closed classification of the value types supplied by Causeway's metamodel.
+ *
+ * <p>The corresponding integration test deliberately fails when the framework adds a value-semantics
+ * provider without extending this inventory.</p>
+ */
+@UtilityClass
+public class BuiltInValueTypeClassifications {
+
+    public enum Classification {
+        REVERSIBLE,
+        STRUCTURED,
+        PROTECTED,
+        OUTPUT_ONLY,
+        UNSUPPORTED
+    }
+
+    private static final Map<String, Classification> BY_CLASS_NAME = Map.ofEntries(
+            reversible("java.lang.Boolean"),
+            reversible("java.lang.Byte"),
+            reversible("java.lang.Character"),
+            reversible("java.lang.Double"),
+            reversible("java.lang.Float"),
+            reversible("java.lang.Integer"),
+            reversible("java.lang.Long"),
+            reversible("java.lang.Short"),
+            reversible("java.lang.String"),
+            reversible("java.math.BigDecimal"),
+            reversible("java.math.BigInteger"),
+            reversible("java.net.URL"),
+            reversible("java.sql.Date"),
+            reversible("java.sql.Time"),
+            reversible("java.sql.Timestamp"),
+            reversible("java.time.LocalDate"),
+            reversible("java.time.LocalDateTime"),
+            reversible("java.time.LocalTime"),
+            reversible("java.time.OffsetDateTime"),
+            reversible("java.time.OffsetTime"),
+            reversible("java.time.ZonedDateTime"),
+            reversible("java.util.Date"),
+            reversible("java.util.Locale"),
+            reversible("java.util.UUID"),
+            reversible("org.apache.causeway.applib.services.appfeat.ApplicationFeatureId"),
+            reversible("org.apache.causeway.applib.services.bookmark.Bookmark"),
+            structured("org.apache.causeway.applib.value.Blob"),
+            structured("org.apache.causeway.applib.value.Clob"),
+            structured("org.apache.causeway.applib.value.LocalResourcePath"),
+            protectedValue("org.apache.causeway.applib.value.Password"),
+            outputOnly("org.apache.causeway.applib.value.Markup"),
+            unsupported("java.awt.image.BufferedImage"),
+            unsupported("org.apache.causeway.applib.Identifier"),
+            unsupported("org.apache.causeway.applib.graph.tree.TreeNode"),
+            unsupported("org.apache.causeway.applib.graph.tree.TreePath"),
+            unsupported("org.apache.causeway.schema.chg.v2.ChangesDto"),
+            unsupported("org.apache.causeway.schema.cmd.v2.CommandDto"),
+            unsupported("org.apache.causeway.schema.common.v2.OidDto"),
+            unsupported("org.apache.causeway.schema.ixn.v2.InteractionDto"));
+
+    public static Optional<Classification> classificationFor(final Class<?> javaType) {
+        return javaType.isEnum()
+                ? Optional.of(Classification.REVERSIBLE)
+                : Optional.ofNullable(BY_CLASS_NAME.get(javaType.getName()));
+    }
+
+    public static Set<String> classNames() {
+        return BY_CLASS_NAME.keySet();
+    }
+
+    private static Map.Entry<String, Classification> reversible(final String className) {
+        return Map.entry(className, Classification.REVERSIBLE);
+    }
+
+    private static Map.Entry<String, Classification> structured(final String className) {
+        return Map.entry(className, Classification.STRUCTURED);
+    }
+
+    private static Map.Entry<String, Classification> protectedValue(final String className) {
+        return Map.entry(className, Classification.PROTECTED);
+    }
+
+    private static Map.Entry<String, Classification> outputOnly(final String className) {
+        return Map.entry(className, Classification.OUTPUT_ONLY);
+    }
+
+    private static Map.Entry<String, Classification> unsupported(final String className) {
+        return Map.entry(className, Classification.UNSUPPORTED);
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalars.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalars.java
new file mode 100644
index 00000000000..093719a1cbf
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalars.java
@@ -0,0 +1,292 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.types;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Locale;
+import java.util.function.Function;
+
+import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.applib.value.Markup;
+import org.apache.causeway.applib.value.Password;
+
+import graphql.GraphQLContext;
+import graphql.execution.CoercedVariables;
+import graphql.language.StringValue;
+import graphql.language.Value;
+import graphql.schema.Coercing;
+import graphql.schema.CoercingParseLiteralException;
+import graphql.schema.CoercingParseValueException;
+import graphql.schema.CoercingSerializeException;
+import graphql.schema.GraphQLScalarType;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class GraphQLValueScalars {
+
+    public static final GraphQLScalarType LOCAL_DATE_TIME = stringScalar(
+            "LocalDateTime",
+            "An ISO-8601 local date-time without an offset or zone; fractional seconds are preserved.",
+            LocalDateTime.class,
+            value -> LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME),
+            value -> DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value));
+
+    public static final GraphQLScalarType URL_VALUE = stringScalar(
+            "Url",
+            "An absolute URL represented by its normalized external form.",
+            URL.class,
+            GraphQLValueScalars::parseUrl,
+            URL::toExternalForm);
+
+    public static final GraphQLScalarType LEGACY_DATE_TIME = stringScalar(
+            "LegacyDateTime",
+            "An ISO-8601 UTC instant used for java.util.Date.",
+            Date.class,
+            value -> Date.from(Instant.parse(value)),
+            value -> DateTimeFormatter.ISO_INSTANT.format(value.toInstant()));
+
+    public static final GraphQLScalarType SQL_DATE = stringScalar(
+            "SqlDate",
+            "An ISO-8601 date-only value used for java.sql.Date.",
+            java.sql.Date.class,
+            value -> java.sql.Date.valueOf(LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE)),
+            value -> DateTimeFormatter.ISO_LOCAL_DATE.format(value.toLocalDate()));
+
+    public static final GraphQLScalarType SQL_TIMESTAMP = stringScalar(
+            "SqlTimestamp",
+            "An ISO-8601 local date-time used for java.sql.Timestamp; fractional seconds are preserved.",
+            Timestamp.class,
+            value -> Timestamp.valueOf(LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME)),
+            value -> DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value.toLocalDateTime()));
+
+    public static final GraphQLScalarType SQL_TIME = stringScalar(
+            "SqlTime",
+            "An ISO-8601 time-only value at second precision used for java.sql.Time.",
+            Time.class,
+            GraphQLValueScalars::parseSqlTime,
+            value -> DateTimeFormatter.ISO_LOCAL_TIME.format(value.toLocalTime()));
+
+    public static final GraphQLScalarType LOCALE = stringScalar(
+            "Locale",
+            "An IETF BCP 47 language tag used for java.util.Locale.",
+            Locale.class,
+            GraphQLValueScalars::parseLocale,
+            Locale::toLanguageTag);
+
+    public static final GraphQLScalarType BOOKMARK = stringScalar(
+            "Bookmark",
+            "A Causeway bookmark in logical-type:identifier form.",
+            Bookmark.class,
+            Bookmark::parseElseFail,
+            Bookmark::stringify);
+
+    public static final GraphQLScalarType APPLICATION_FEATURE_ID = stringScalar(
+            "ApplicationFeatureId",
+            "A URL-safe encoded Causeway application feature identifier.",
+            ApplicationFeatureId.class,
+            ApplicationFeatureId::parseEncoded,
+            ApplicationFeatureId::asEncodedString);
+
+    public static final GraphQLScalarType PASSWORD = stringScalar(
+            "Password",
+            "A protected Causeway password value. Input constructs the value; output is always suppressed.",
+            Password.class,
+            Password::of,
+            Password::toString);
+
+    public static final GraphQLScalarType MARKUP = outputOnlyStringScalar(
+            "Markup",
+            "Causeway markup HTML. This scalar is output-only and clients must treat the content as markup.",
+            Markup.class,
+            Markup::html);
+
+    public static final GraphQLScalarType UNSUPPORTED_VALUE = GraphQLScalarType.newScalar()
+            .name("UnsupportedValue")
+            .description("Unsupported Causeway value. Strict output is redacted and input requires an explicit reversible ScalarMarshaller or TypeMapper.")
+            .coercing(new Coercing<Object, String>() {
+                @Override
+                public String serialize(
+                        final Object dataFetcherResult,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    return "[unsupported]";
+                }
+
+                @Override
+                public Object parseValue(
+                        final Object input,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    throw new CoercingParseValueException(
+                            "This Causeway value has no reversible GraphQL input strategy; register a ScalarMarshaller or TypeMapper");
+                }
+
+                @Override
+                public Object parseLiteral(
+                        final Value<?> input,
+                        final CoercedVariables variables,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    throw new CoercingParseLiteralException(
+                            "This Causeway value has no reversible GraphQL input strategy; register a ScalarMarshaller or TypeMapper");
+                }
+            })
+            .build();
+
+    /**
+     * Compatibility alias used where the unsupported scalar is selected specifically for input.
+     */
+    public static final GraphQLScalarType UNSUPPORTED_INPUT = UNSUPPORTED_VALUE;
+
+    private static URL parseUrl(final String value) {
+        try {
+            var url = new URL(value);
+            if (url.getProtocol() == null || url.getHost() == null || url.getHost().isBlank()) {
+                throw new IllegalArgumentException("URL must be absolute");
+            }
+            return url;
+        } catch (MalformedURLException ex) {
+            throw new IllegalArgumentException("Invalid URL", ex);
+        }
+    }
+
+    private static Time parseSqlTime(final String value) {
+        var localTime = java.time.LocalTime.parse(value, DateTimeFormatter.ISO_LOCAL_TIME);
+        if (localTime.getNano() != 0) {
+            throw new IllegalArgumentException("SqlTime supports second precision");
+        }
+        return Time.valueOf(localTime);
+    }
+
+    private static Locale parseLocale(final String value) {
+        return new Locale.Builder().setLanguageTag(value).build();
+    }
+
+    private static <T> GraphQLScalarType stringScalar(
+            final String name,
+            final String description,
+            final Class<T> javaType,
+            final Function<String, T> parser,
+            final Function<T, String> formatter) {
+        return GraphQLScalarType.newScalar()
+                .name(name)
+                .description(description)
+                .coercing(new Coercing<T, String>() {
+                    @Override
+                    public String serialize(
+                            final Object dataFetcherResult,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        if (!javaType.isInstance(dataFetcherResult)) {
+                            throw new CoercingSerializeException("Expected a " + name + " compatible Java value");
+                        }
+                        try {
+                            return formatter.apply(javaType.cast(dataFetcherResult));
+                        } catch (RuntimeException ex) {
+                            throw new CoercingSerializeException("Unable to serialize " + name, ex);
+                        }
+                    }
+
+                    @Override
+                    public T parseValue(
+                            final Object input,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        if (!(input instanceof String text)) {
+                            throw new CoercingParseValueException("Expected " + name + " input as a string");
+                        }
+                        return parseString(text, parser, name, CoercingParseValueException::new);
+                    }
+
+                    @Override
+                    public T parseLiteral(
+                            final Value<?> input,
+                            final CoercedVariables variables,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        if (!(input instanceof StringValue stringValue)) {
+                            throw new CoercingParseLiteralException("Expected " + name + " input as a string literal");
+                        }
+                        return parseString(stringValue.getValue(), parser, name, CoercingParseLiteralException::new);
+                    }
+                })
+                .build();
+    }
+
+    private static <T> GraphQLScalarType outputOnlyStringScalar(
+            final String name,
+            final String description,
+            final Class<T> javaType,
+            final Function<T, String> formatter) {
+        return GraphQLScalarType.newScalar()
+                .name(name)
+                .description(description)
+                .coercing(new Coercing<T, String>() {
+                    @Override
+                    public String serialize(
+                            final Object dataFetcherResult,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        if (!javaType.isInstance(dataFetcherResult)) {
+                            throw new CoercingSerializeException("Expected a " + name + " compatible Java value");
+                        }
+                        return formatter.apply(javaType.cast(dataFetcherResult));
+                    }
+
+                    @Override
+                    public T parseValue(
+                            final Object input,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        throw new CoercingParseValueException(name + " is output-only");
+                    }
+
+                    @Override
+                    public T parseLiteral(
+                            final Value<?> input,
+                            final CoercedVariables variables,
+                            final GraphQLContext graphQLContext,
+                            final Locale locale) {
+                        throw new CoercingParseLiteralException(name + " is output-only");
+                    }
+                })
+                .build();
+    }
+
+    private static <T, E extends RuntimeException> T parseString(
+            final String value,
+            final Function<String, T> parser,
+            final String scalarName,
+            final Function<String, E> exceptionFactory) {
+        try {
+            return parser.apply(value);
+        } catch (RuntimeException ex) {
+            throw exceptionFactory.apply("Invalid " + scalarName + " value");
+        }
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ResourceValueTypes.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ResourceValueTypes.java
new file mode 100644
index 00000000000..b681d06af77
--- /dev/null
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ResourceValueTypes.java
@@ -0,0 +1,436 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.types;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.causeway.applib.value.Blob;
+import org.apache.causeway.applib.value.Clob;
+import org.apache.causeway.applib.value.LocalResourcePath;
+import org.apache.causeway.applib.value.OpenUrlStrategy;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.causeway.core.metamodel.util.Facets;
+import org.apache.causeway.viewer.graphql.model.context.Context;
+
+import graphql.Scalars;
+import graphql.schema.DataFetcher;
+import graphql.schema.FieldCoordinates;
+import graphql.schema.GraphQLEnumType;
+import graphql.schema.GraphQLInputObjectField;
+import graphql.schema.GraphQLInputObjectType;
+import graphql.schema.GraphQLInputType;
+import graphql.schema.GraphQLObjectType;
+import graphql.schema.GraphQLOutputType;
+import lombok.experimental.UtilityClass;
+
+import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
+import static graphql.schema.GraphQLNonNull.nonNull;
+
+@UtilityClass
+public class ResourceValueTypes {
+
+    public static final String BLOB_INPUT_TYPE = "BlobInput";
+    public static final String CLOB_INPUT_TYPE = "ClobInput";
+    public static final String BLOB_OUTPUT_TYPE = "BlobValue";
+    public static final String CLOB_OUTPUT_TYPE = "ClobValue";
+    public static final String LOCAL_RESOURCE_PATH_INPUT_TYPE = "LocalResourcePathInput";
+    public static final String LOCAL_RESOURCE_PATH_OUTPUT_TYPE = "LocalResourcePathValue";
+    public static final String OPEN_URL_STRATEGY_TYPE = "OpenUrlStrategy";
+
+    public static boolean isResourceType(final Class<?> javaType) {
+        return javaType == Blob.class || javaType == Clob.class;
+    }
+
+    public static boolean isLocalResourcePathType(final Class<?> javaType) {
+        return javaType == LocalResourcePath.class;
+    }
+
+    public static GraphQLInputType inputTypeFor(final Class<?> javaType, final Context context) {
+        if (!valueContentEnabled(context)) {
+            return GraphQLValueScalars.UNSUPPORTED_INPUT;
+        }
+        return javaType == Blob.class
+                ? blobInputType(context)
+                : clobInputType(context);
+    }
+
+    public static GraphQLOutputType outputTypeFor(final Class<?> javaType, final Context context) {
+        return javaType == Blob.class
+                ? blobOutputType(context)
+                : clobOutputType(context);
+    }
+
+    public static GraphQLInputType localResourcePathInputType(final Context context) {
+        var existing = context.graphQLTypeRegistry.lookup(LOCAL_RESOURCE_PATH_INPUT_TYPE, GraphQLInputObjectType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var type = GraphQLInputObjectType.newInputObject()
+                .name(LOCAL_RESOURCE_PATH_INPUT_TYPE)
+                .description("A Causeway local resource path and browser opening strategy.")
+                .field(GraphQLInputObjectField.newInputObjectField()
+                        .name("path")
+                        .type(nonNull(Scalars.GraphQLString)))
+                .field(GraphQLInputObjectField.newInputObjectField()
+                        .name("openUrlStrategy")
+                        .type(nonNull(openUrlStrategyType(context))))
+                .build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        return type;
+    }
+
+    public static GraphQLOutputType localResourcePathOutputType(final Context context) {
+        var existing = context.graphQLTypeRegistry.lookup(LOCAL_RESOURCE_PATH_OUTPUT_TYPE, GraphQLObjectType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var type = GraphQLObjectType.newObject()
+                .name(LOCAL_RESOURCE_PATH_OUTPUT_TYPE)
+                .description("A Causeway local resource path and browser opening strategy.")
+                .field(newFieldDefinition()
+                        .name("path")
+                        .type(nonNull(Scalars.GraphQLString)))
+                .field(newFieldDefinition()
+                        .name("openUrlStrategy")
+                        .type(nonNull(openUrlStrategyType(context))))
+                .build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        register(context, type.getName(), "path", source -> ((LocalResourcePath) source).path());
+        register(context, type.getName(), "openUrlStrategy",
+                source -> ((LocalResourcePath) source).openUrlStrategy().name());
+        return type;
+    }
+
+    public static LocalResourcePath unmarshalLocalResourcePath(final Object graphValue) {
+        if (!(graphValue instanceof Map<?, ?> input)) {
+            throw new IllegalArgumentException("Expected structured LocalResourcePath input");
+        }
+        var path = requiredString(input, "path", 2083);
+        var strategyName = requiredString(input, "openUrlStrategy", 64);
+        final OpenUrlStrategy strategy;
+        try {
+            strategy = OpenUrlStrategy.valueOf(strategyName);
+        } catch (IllegalArgumentException ex) {
+            throw new IllegalArgumentException("Invalid LocalResourcePath open strategy");
+        }
+        return new LocalResourcePath(path, strategy);
+    }
+
+    public static Object unmarshal(final Class<?> javaType, final Object graphValue, final Context context) {
+        if (!valueContentEnabled(context)) {
+            throw new IllegalArgumentException("Resource input is forbidden by GraphQL value-content policy");
+        }
+        if (!(graphValue instanceof Map<?, ?> input)) {
+            throw new IllegalArgumentException("Expected structured resource input");
+        }
+        var name = requiredString(input, "name", 255);
+        var mimeType = requiredString(input, "mimeType", 255);
+        requireValidMimeType(mimeType);
+        var maxBytes = resources(context).inlineInputMaxBytes();
+
+        if (javaType == Blob.class) {
+            var base64 = requiredContent(input, "base64");
+            if (base64.length() > encodedLengthLimit(maxBytes)) {
+                throw new IllegalArgumentException("Resource input exceeds the configured inline byte limit");
+            }
+            final byte[] bytes;
+            try {
+                bytes = Base64.getDecoder().decode(base64);
+            } catch (IllegalArgumentException ex) {
+                throw new IllegalArgumentException("Blob input contains invalid base64 content");
+            }
+            requireWithinLimit(bytes.length, maxBytes);
+            return new Blob(name, mimeType, bytes);
+        }
+
+        var chars = requiredContent(input, "chars");
+        if (chars.length() > maxBytes) {
+            throw new IllegalArgumentException("Resource input exceeds the configured inline byte limit");
+        }
+        var bytes = chars.getBytes(StandardCharsets.UTF_8);
+        requireWithinLimit(bytes.length, maxBytes);
+        return new Clob(name, mimeType, chars);
+    }
+
+    public static void validateFileAccept(final ObjectFeature feature, final Object resourceValue) {
+        if (!(resourceValue instanceof Blob) && !(resourceValue instanceof Clob)) {
+            return;
+        }
+        var accepted = fileAccept(feature);
+        if (accepted.isEmpty()) {
+            return;
+        }
+        var name = resourceValue instanceof Blob blob ? blob.name() : ((Clob) resourceValue).name();
+        var mimeType = resourceValue instanceof Blob blob
+                ? blob.mimeType().getBaseType()
+                : ((Clob) resourceValue).mimeType().getBaseType();
+        if (!matchesFileAccept(accepted.get(), name, mimeType)) {
+            throw new IllegalArgumentException(
+                    "Resource media type is not accepted for member '" + feature.getFeatureIdentifier() + "'");
+        }
+    }
+
+    public static Optional<String> fileAccept(final ObjectFeature feature) {
+        return Facets.fileAccept(feature)
+                .filter(value -> !value.isBlank());
+    }
+
+    public static int byteLength(final Blob blob) {
+        return blob.bytes().length;
+    }
+
+    public static int byteLength(final Clob clob) {
+        return clob.chars().toString().getBytes(StandardCharsets.UTF_8).length;
+    }
+
+    public static String transferMode(final int byteLength, final Context context) {
+        return valueContentEnabled(context) && byteLength <= resources(context).inlineOutputMaxBytes()
+                ? "INLINE"
+                : "METADATA_ONLY";
+    }
+
+    public static String inputMode(final Context context) {
+        return valueContentEnabled(context) ? "STRUCTURED_INLINE" : "FORBIDDEN";
+    }
+
+    private static GraphQLEnumType openUrlStrategyType(final Context context) {
+        var existing = context.graphQLTypeRegistry.lookup(OPEN_URL_STRATEGY_TYPE, GraphQLEnumType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var builder = GraphQLEnumType.newEnum()
+                .name(OPEN_URL_STRATEGY_TYPE)
+                .description("Browser opening strategy for a Causeway local resource path.");
+        for (var strategy : OpenUrlStrategy.values()) {
+            builder.value(strategy.name());
+        }
+        var type = builder.build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        return type;
+    }
+
+    private static GraphQLInputObjectType blobInputType(final Context context) {
+        return inputType(context, BLOB_INPUT_TYPE, "Bounded inline Causeway Blob input.", "base64");
+    }
+
+    private static GraphQLInputObjectType clobInputType(final Context context) {
+        return inputType(context, CLOB_INPUT_TYPE, "Bounded inline Causeway Clob input.", "chars");
+    }
+
+    private static GraphQLInputObjectType inputType(
+            final Context context,
+            final String typeName,
+            final String description,
+            final String contentField) {
+        var existing = context.graphQLTypeRegistry.lookup(typeName, GraphQLInputObjectType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var type = GraphQLInputObjectType.newInputObject()
+                .name(typeName)
+                .description(description)
+                .field(inputField("name", "Resource filename."))
+                .field(inputField("mimeType", "IANA media type accepted by the target member."))
+                .field(inputField(contentField, contentField.equals("base64")
+                        ? "RFC 4648 base64 content, bounded after decoding."
+                        : "UTF-8 character content, bounded by encoded byte length."))
+                .build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        return type;
+    }
+
+    private static GraphQLInputObjectField inputField(final String name, final String description) {
+        return GraphQLInputObjectField.newInputObjectField()
+                .name(name)
+                .description(description)
+                .type(nonNull(Scalars.GraphQLString))
+                .build();
+    }
+
+    private static GraphQLObjectType blobOutputType(final Context context) {
+        var existing = context.graphQLTypeRegistry.lookup(BLOB_OUTPUT_TYPE, GraphQLObjectType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var builder = commonOutputBuilder(BLOB_OUTPUT_TYPE, "Metadata-first bounded Causeway Blob output.");
+        if (valueContentEnabled(context)) {
+            builder.field(newFieldDefinition()
+                    .name("base64")
+                    .description("RFC 4648 base64 content; null when the configured inline-output limit is exceeded.")
+                    .type(Scalars.GraphQLString));
+        }
+        var type = builder.build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        registerCommonOutputFetchers(context, type.getName());
+        register(context, type.getName(), "name", source -> ((Blob) source).name());
+        register(context, type.getName(), "mimeType", source -> ((Blob) source).mimeType().getBaseType());
+        register(context, type.getName(), "byteLength", source -> byteLength((Blob) source));
+        if (valueContentEnabled(context)) {
+            register(context, type.getName(), "base64", source -> {
+                var blob = (Blob) source;
+                return byteLength(blob) <= resources(context).inlineOutputMaxBytes()
+                        ? Base64.getEncoder().encodeToString(blob.bytes())
+                        : null;
+            });
+        }
+        return type;
+    }
+
+    private static GraphQLObjectType clobOutputType(final Context context) {
+        var existing = context.graphQLTypeRegistry.lookup(CLOB_OUTPUT_TYPE, GraphQLObjectType.class);
+        if (existing.isPresent()) {
+            return existing.get();
+        }
+        var builder = commonOutputBuilder(CLOB_OUTPUT_TYPE, "Metadata-first bounded Causeway Clob output.")
+                .field(newFieldDefinition().name("characterLength").type(nonNull(Scalars.GraphQLInt)));
+        if (valueContentEnabled(context)) {
+            builder.field(newFieldDefinition()
+                    .name("chars")
+                    .description("Character content; null when the configured UTF-8 inline-output limit is exceeded.")
+                    .type(Scalars.GraphQLString));
+        }
+        var type = builder.build();
+        context.graphQLTypeRegistry.addTypeIfNotAlreadyPresent(type);
+        registerCommonOutputFetchers(context, type.getName());
+        register(context, type.getName(), "name", source -> ((Clob) source).name());
+        register(context, type.getName(), "mimeType", source -> ((Clob) source).mimeType().getBaseType());
+        register(context, type.getName(), "byteLength", source -> byteLength((Clob) source));
+        register(context, type.getName(), "characterLength", source -> ((Clob) source).chars().length());
+        if (valueContentEnabled(context)) {
+            register(context, type.getName(), "chars", source -> {
+                var clob = (Clob) source;
+                return byteLength(clob) <= resources(context).inlineOutputMaxBytes()
+                        ? clob.chars().toString()
+                        : null;
+            });
+        }
+        return type;
+    }
+
+    private static GraphQLObjectType.Builder commonOutputBuilder(
+            final String name,
+            final String description) {
+        return GraphQLObjectType.newObject()
+                .name(name)
+                .description(description)
+                .field(newFieldDefinition().name("name").type(nonNull(Scalars.GraphQLString)))
+                .field(newFieldDefinition().name("mimeType").type(nonNull(Scalars.GraphQLString)))
+                .field(newFieldDefinition().name("byteLength").type(nonNull(Scalars.GraphQLInt)))
+                .field(newFieldDefinition().name("transferMode").type(nonNull(Scalars.GraphQLString)))
+                .field(newFieldDefinition().name("inlineOutputMaxBytes").type(nonNull(Scalars.GraphQLInt)));
+    }
+
+    private static void registerCommonOutputFetchers(final Context context, final String typeName) {
+        register(context, typeName, "transferMode", source -> source instanceof Blob blob
+                ? transferMode(byteLength(blob), context)
+                : transferMode(byteLength((Clob) source), context));
+        register(context, typeName, "inlineOutputMaxBytes", source -> resources(context).inlineOutputMaxBytes());
+    }
+
+    private static void register(
+            final Context context,
+            final String typeName,
+            final String fieldName,
+            final FunctionWithSource function) {
+        context.codeRegistryBuilder.dataFetcher(
+                FieldCoordinates.coordinates(typeName, fieldName),
+                (DataFetcher<Object>) environment -> function.apply(environment.getSource()));
+    }
+
+    private static String requiredString(
+            final Map<?, ?> input,
+            final String field,
+            final int maxCharacters) {
+        var value = input.get(field);
+        if (!(value instanceof String text)
+                || text.isBlank()
+                || text.length() > maxCharacters) {
+            throw new IllegalArgumentException("Resource input field '" + field + "' is missing or invalid");
+        }
+        return text;
+    }
+
+    private static String requiredContent(final Map<?, ?> input, final String field) {
+        var value = input.get(field);
+        if (!(value instanceof String text)) {
+            throw new IllegalArgumentException("Resource input field '" + field + "' is missing or invalid");
+        }
+        return text;
+    }
+
+    private static int encodedLengthLimit(final int maxBytes) {
+        var encodedLimit = ((long) maxBytes + 2L) / 3L * 4L + 4L;
+        return (int) Math.min(Integer.MAX_VALUE, encodedLimit);
+    }
+
+    private static void requireWithinLimit(final int actualBytes, final int maxBytes) {
+        if (actualBytes > maxBytes) {
+            throw new IllegalArgumentException("Resource input exceeds the configured inline byte limit");
+        }
+    }
+
+    private static void requireValidMimeType(final String mimeType) {
+        if (!mimeType.matches("[A-Za-z0-9!#$&^_.+\\-]+/[A-Za-z0-9!#$&^_.+\\-]+")) {
+            throw new IllegalArgumentException("Resource input media type is invalid");
+        }
+    }
+
+    private static boolean matchesFileAccept(
+            final String accepted,
+            final String name,
+            final String mimeType) {
+        var lowerName = name.toLowerCase(Locale.ROOT);
+        var lowerMimeType = mimeType.toLowerCase(Locale.ROOT);
+        for (String candidate : accepted.toLowerCase(Locale.ROOT).split("[,|\\s]+")) {
+            if (candidate.isBlank()) {
+                continue;
+            }
+            if (candidate.startsWith(".") && lowerName.endsWith(candidate)) {
+                return true;
+            }
+            if (candidate.endsWith("/*")
+                    && lowerMimeType.startsWith(candidate.substring(0, candidate.length() - 1))) {
+                return true;
+            }
+            if (candidate.equals(lowerMimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean valueContentEnabled(final Context context) {
+        return resources(context).effectiveValueContentResponseType()
+                != CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN;
+    }
+
+    private static CausewayConfiguration.Viewer.Graphql.Resources resources(final Context context) {
+        return context.causewayConfiguration.viewer().graphql().resources();
+    }
+
+    @FunctionalInterface
+    private interface FunctionWithSource {
+        Object apply(Object source);
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
index bb7efbcacfd..ae1950e8838 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
@@ -27,6 +27,10 @@ public interface ScalarMapper {
 
     GraphQLScalarType scalarTypeFor(final Class<?> clazz);
 
+    GraphQLScalarType inputScalarTypeFor(final Class<?> clazz);
+
+    boolean supportsInput(final Class<?> clazz);
+
     Object unmarshal(
             final Object argumentValue,
             final Class<?> targetType);
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapperUsingScalarMarshallers.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapperUsingScalarMarshallers.java
index 5b7c5bb2e36..3cd24170722 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapperUsingScalarMarshallers.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapperUsingScalarMarshallers.java
@@ -64,12 +64,29 @@ public GraphQLScalarType scalarTypeFor(final Class<?> clazz){
         return scalarMarshallerFor(clazz).getGqlScalarType();
     }
 
+    @Override
+    public GraphQLScalarType inputScalarTypeFor(final Class<?> clazz) {
+        var scalarMarshaller = scalarMarshallerFor(clazz);
+        return scalarMarshaller.supportsInput()
+                ? scalarMarshaller.getGqlScalarType()
+                : GraphQLValueScalars.UNSUPPORTED_INPUT;
+    }
+
+    @Override
+    public boolean supportsInput(final Class<?> clazz) {
+        return scalarMarshallerFor(clazz).supportsInput();
+    }
+
     @Override
     public Object unmarshal(
             final Object argumentValue,
             final Class<?> targetType) {
 
         ScalarMarshaller<?> scalarMarshaller = scalarMarshallerFor(targetType);
+        if (!scalarMarshaller.supportsInput()) {
+            throw new IllegalArgumentException(
+                    "No reversible GraphQL input strategy is registered for " + targetType.getName());
+        }
         return scalarMarshaller.unmarshal(argumentValue, targetType);
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
index 22109f952ce..fdd7a3b07a8 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
@@ -66,6 +66,10 @@ public TypeMapper defaultTypeMapper(final ScalarMapper scalarMapper, final Provi
     public GraphQLOutputType outputTypeFor(final Class<?> clazz){
         if (clazz.isEnum())
 			return contextProvider.get().graphQLTypeRegistry.addEnumTypeIfNotAlreadyPresent(clazz, SchemaType.RICH);
+        if (ResourceValueTypes.isResourceType(clazz))
+            return ResourceValueTypes.outputTypeFor(clazz, contextProvider.get());
+        if (ResourceValueTypes.isLocalResourcePathType(clazz))
+            return ResourceValueTypes.localResourcePathOutputType(contextProvider.get());
         return scalarMapper.scalarTypeFor(clazz);
     }
 
@@ -73,7 +77,11 @@ public GraphQLOutputType outputTypeFor(final Class<?> clazz){
     public GraphQLInputType inputTypeFor(final Class<?> clazz){
         if (clazz.isEnum())
 			return contextProvider.get().graphQLTypeRegistry.addEnumTypeIfNotAlreadyPresent(clazz, SchemaType.RICH);
-        return scalarMapper.scalarTypeFor(clazz);
+        if (ResourceValueTypes.isResourceType(clazz))
+            return ResourceValueTypes.inputTypeFor(clazz, contextProvider.get());
+        if (ResourceValueTypes.isLocalResourcePathType(clazz))
+            return ResourceValueTypes.localResourcePathInputType(contextProvider.get());
+        return scalarMapper.inputScalarTypeFor(clazz);
     }
 
     @Override
@@ -83,6 +91,10 @@ public Object unmarshal(
         var correspondingClass = targetObjectSpec.getCorrespondingClass();
         if (correspondingClass.isEnum())
 			return gqlValue;
+        if (ResourceValueTypes.isResourceType(correspondingClass))
+            return ResourceValueTypes.unmarshal(correspondingClass, gqlValue, contextProvider.get());
+        if (ResourceValueTypes.isLocalResourcePathType(correspondingClass))
+            return ResourceValueTypes.unmarshalLocalResourcePath(gqlValue);
         return scalarMapper.unmarshal(gqlValue, correspondingClass);
     }
 
@@ -226,7 +238,7 @@ private GraphQLInputType inputTypeFor_(
             case VALUE -> inputTypeFor(elementObjectSpec.getCorrespondingClass());
             case COLLECTION ->
                 throw new IllegalArgumentException(String.format("OneToOneFeature '%s' is not expected to have a beanSort of COLLECTION", oneToOneFeature.getFeatureIdentifier().toString()));
-            default -> Scalars.GraphQLString; // for now
+            default -> GraphQLValueScalars.UNSUPPORTED_INPUT;
         };
     }
 
@@ -247,7 +259,7 @@ public GraphQLInputType inputTypeFor(
             case VALUE -> inputTypeFor(elementType.getCorrespondingClass());
             case COLLECTION ->
                 throw new IllegalArgumentException(String.format("ObjectSpec '%s' is not expected to have a beanSort of COLLECTION", elementType.getFullIdentifier()));
-            default -> Scalars.GraphQLString; // for now
+            default -> GraphQLValueScalars.UNSUPPORTED_INPUT;
         };
     }
 
diff --git a/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ResourcePathTest.java b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ResourcePathTest.java
index 5755688d515..90f5dd7bc6b 100644
--- a/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ResourcePathTest.java
+++ b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ResourcePathTest.java
@@ -95,6 +95,8 @@ void categoryPoliciesFallBackToLegacySettingUnlessOverridden() {
                 CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT,
                 null,
                 null,
+                1_048_576,
+                1_048_576,
                 null);
         assertEquals(
                 CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT,
@@ -107,6 +109,8 @@ void categoryPoliciesFallBackToLegacySettingUnlessOverridden() {
                 CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT,
                 CausewayConfiguration.Viewer.Graphql.ResponseType.DIRECT,
                 CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN,
+                1_048_576,
+                1_048_576,
                 "/public");
         assertEquals(
                 CausewayConfiguration.Viewer.Graphql.ResponseType.DIRECT,
diff --git a/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalarsTest.java b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalarsTest.java
new file mode 100644
index 00000000000..0de07217780
--- /dev/null
+++ b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/GraphQLValueScalarsTest.java
@@ -0,0 +1,112 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.types;
+
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.Locale;
+
+import graphql.GraphQLContext;
+import graphql.schema.CoercingParseValueException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class GraphQLValueScalarsTest {
+
+    private static final GraphQLContext GRAPHQL_CONTEXT = GraphQLContext.newContext().build();
+
+    @Test
+    void localDateTimePreservesFractionalPrecision() {
+        var scalar = GraphQLValueScalars.LOCAL_DATE_TIME.getCoercing();
+        var parsed = scalar.parseValue("2026-08-18T09:10:11.123456789", GRAPHQL_CONTEXT, Locale.ROOT);
+        assertEquals(LocalDateTime.of(2026, 8, 18, 9, 10, 11, 123_456_789), parsed);
+        assertEquals("2026-08-18T09:10:11.123456789", scalar.serialize(parsed, GRAPHQL_CONTEXT, Locale.ROOT));
+    }
+
+    @Test
+    void legacyDateUsesUtcInstant() {
+        var scalar = GraphQLValueScalars.LEGACY_DATE_TIME.getCoercing();
+        var parsed = scalar.parseValue("2026-08-18T08:10:11.123Z", GRAPHQL_CONTEXT, Locale.ROOT);
+        assertEquals(Date.from(Instant.parse("2026-08-18T08:10:11.123Z")), parsed);
+        assertEquals("2026-08-18T08:10:11.123Z", scalar.serialize(parsed, GRAPHQL_CONTEXT, Locale.ROOT));
+    }
+
+    @Test
+    void sqlDateTimeAndTimestampRetainTheirDistinctSemantics() {
+        var dateScalar = GraphQLValueScalars.SQL_DATE.getCoercing();
+        var timeScalar = GraphQLValueScalars.SQL_TIME.getCoercing();
+        var timestampScalar = GraphQLValueScalars.SQL_TIMESTAMP.getCoercing();
+
+        var date = dateScalar.parseValue("2026-08-18", GRAPHQL_CONTEXT, Locale.ROOT);
+        var time = timeScalar.parseValue("09:10:11", GRAPHQL_CONTEXT, Locale.ROOT);
+        var timestamp = timestampScalar.parseValue(
+                "2026-08-18T09:10:11.123456789",
+                GRAPHQL_CONTEXT,
+                Locale.ROOT);
+
+        assertEquals(java.sql.Date.valueOf("2026-08-18"), date);
+        assertEquals(Time.valueOf("09:10:11"), time);
+        assertEquals(Timestamp.valueOf("2026-08-18 09:10:11.123456789"), timestamp);
+        assertEquals("2026-08-18", dateScalar.serialize(date, GRAPHQL_CONTEXT, Locale.ROOT));
+        assertEquals("09:10:11", timeScalar.serialize(time, GRAPHQL_CONTEXT, Locale.ROOT));
+        assertThrows(
+                CoercingParseValueException.class,
+                () -> timeScalar.parseValue("09:10:11.123", GRAPHQL_CONTEXT, Locale.ROOT));
+        assertEquals(
+                "2026-08-18T09:10:11.123456789",
+                timestampScalar.serialize(timestamp, GRAPHQL_CONTEXT, Locale.ROOT));
+    }
+
+    @Test
+    void localeUsesCanonicalLanguageTags() {
+        var scalar = GraphQLValueScalars.LOCALE.getCoercing();
+        var parsed = scalar.parseValue("en-GB", GRAPHQL_CONTEXT, Locale.ROOT);
+        assertEquals(Locale.UK, parsed);
+        assertEquals("en-GB", scalar.serialize(parsed, GRAPHQL_CONTEXT, Locale.ROOT));
+    }
+
+    @Test
+    void unsupportedOutputIsConstantAndDoesNotUseTheValueString() {
+        var scalar = GraphQLValueScalars.UNSUPPORTED_VALUE.getCoercing();
+        assertEquals(
+                "[unsupported]",
+                scalar.serialize("NEVER_DISCLOSE", GRAPHQL_CONTEXT, Locale.ROOT));
+    }
+
+    @Test
+    void malformedAndUnsupportedInputProduceBoundedCoercionErrors() {
+        var malformed = assertThrows(
+                CoercingParseValueException.class,
+                () -> GraphQLValueScalars.LOCAL_DATE_TIME.getCoercing()
+                        .parseValue("not-a-date", GRAPHQL_CONTEXT, Locale.ROOT));
+        var unsupported = assertThrows(
+                CoercingParseValueException.class,
+                () -> GraphQLValueScalars.UNSUPPORTED_INPUT.getCoercing()
+                        .parseValue("secret", GRAPHQL_CONTEXT, Locale.ROOT));
+
+        assertEquals("Invalid LocalDateTime value", malformed.getMessage());
+        assertFalse(unsupported.getMessage().contains("secret"));
+    }
+}
diff --git a/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/ScalarMarshallerInputCapabilityTest.java b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/ScalarMarshallerInputCapabilityTest.java
new file mode 100644
index 00000000000..e979a8ec70e
--- /dev/null
+++ b/viewers/graphql/model/src/test/java/org/apache/causeway/viewer/graphql/model/types/ScalarMarshallerInputCapabilityTest.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.model.types;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
+
+import graphql.Scalars;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ScalarMarshallerInputCapabilityTest {
+
+    @Test
+    void compatibilityConstructorIsOutputOnly() {
+        assertFalse(new TestMarshaller().supportsInput());
+    }
+
+    @Test
+    void reversibleMarshallerMustOptIn() {
+        assertTrue(new ReversibleTestMarshaller().supportsInput());
+    }
+
+    private static class TestMarshaller extends ScalarMarshallerAbstract<String> {
+        private TestMarshaller() {
+            super(String.class, Scalars.GraphQLString, null);
+        }
+
+        @Override
+        public String unmarshal(final Object graphValue, final Class<?> targetType) {
+            return (String) graphValue;
+        }
+    }
+
+    private static class ReversibleTestMarshaller extends ScalarMarshallerAbstract<String> {
+        private ReversibleTestMarshaller() {
+            super(String.class, Scalars.GraphQLString, null, true);
+        }
+
+        @Override
+        public String unmarshal(final Object graphValue, final Class<?> targetType) {
+            return (String) graphValue;
+        }
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/Calculator.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/Calculator.java
index d115beffbb2..7548fc011e8 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/Calculator.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/Calculator.java
@@ -21,11 +21,15 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.URL;
+import java.sql.Time;
+import java.sql.Timestamp;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
 import java.time.OffsetTime;
 import java.time.ZonedDateTime;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.UUID;
@@ -34,12 +38,21 @@
 import jakarta.inject.Inject;
 import jakarta.inject.Named;
 
+import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.annotation.Action;
 import org.apache.causeway.applib.annotation.DomainService;
 import org.apache.causeway.applib.annotation.Optionality;
 import org.apache.causeway.applib.annotation.Parameter;
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
 import org.apache.causeway.applib.annotation.SemanticsOf;
+import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.applib.value.Blob;
+import org.apache.causeway.applib.value.Clob;
+import org.apache.causeway.applib.value.LocalResourcePath;
+import org.apache.causeway.applib.value.Markup;
+import org.apache.causeway.applib.value.Password;
 
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
@@ -171,12 +184,139 @@ public URL someUrl() {
         return new URL("https://causeway.apache.org");
     }
 
+    @Action(semantics = SemanticsOf.SAFE)
+    public LocalDateTime echoLocalDateTime(final LocalDateTime value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public LocalDateTime optionalLocalDateTime(
+            @Parameter(optionality = Optionality.OPTIONAL) final LocalDateTime value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public URL echoUrl(final URL value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Date echoJavaUtilDate(final Date value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public java.sql.Date echoJavaSqlDate(final java.sql.Date value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Time echoJavaSqlTime(final Time value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Timestamp echoJavaSqlTimestamp(final Timestamp value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public ComplexNumber echoComplexNumber(final ComplexNumber value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Blob echoBlob(@Parameter(fileAccept = "text/plain") final Blob value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Blob optionalBlob(
+            @Parameter(optionality = Optionality.OPTIONAL, fileAccept = "text/plain") final Blob value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Clob echoClob(@Parameter(fileAccept = "text/plain") final Clob value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Blob sampleBlob() {
+        return new Blob("sample.txt", "text/plain", "sample blob".getBytes(java.nio.charset.StandardCharsets.UTF_8));
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Clob sampleClob() {
+        return new Clob("sample.txt", "text/plain", "sample clob");
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Password secretPassword() {
+        return Password.of("NEVER_DISCLOSE_THIS_PASSWORD");
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Password echoPassword(final Password value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Bookmark echoBookmark(final Bookmark value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public ApplicationFeatureId echoApplicationFeatureId(final ApplicationFeatureId value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public LocalResourcePath echoLocalResourcePath(final LocalResourcePath value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Markup sampleMarkup() {
+        return Markup.valueOf("<strong>GraphQL markup</strong>");
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Markup echoMarkup(final Markup value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Identifier sampleIdentifier() {
+        return Identifier.classIdentifier(new LogicalType("university.calc.Calculator", Calculator.class));
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Identifier echoIdentifier(final Identifier value) {
+        return value;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public UnmappedValue sampleUnmappedValue() {
+        return new UnmappedValue("NEVER_DISCLOSE_UNMAPPED_VALUE");
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public UnmappedValue echoUnmappedValue(final UnmappedValue value) {
+        return value;
+    }
+
     @SneakyThrows
     @Action(semantics = SemanticsOf.SAFE)
     public Locale someLocale() {
         return Locale.UK;
     }
 
+    @Action(semantics = SemanticsOf.SAFE)
+    public Locale echoLocale(final Locale value) {
+        return value;
+    }
+
     @Action(semantics = SemanticsOf.NON_IDEMPOTENT)
     public EditableMementoViewModel createEditableMementoViewModel() {
         return new EditableMementoViewModel(18);
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumber.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumber.java
new file mode 100644
index 00000000000..7749338f18f
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumber.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
+
+import java.math.BigDecimal;
+import java.util.regex.Pattern;
+
+import jakarta.inject.Named;
+
+import org.apache.causeway.applib.annotation.Value;
+
+@Named("university.calc.ComplexNumber")
+@Value
+public record ComplexNumber(BigDecimal real, BigDecimal imaginary) {
+
+    private static final Pattern CANONICAL_PATTERN = Pattern.compile(
+            "^([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+))([+-](?:\\d+(?:\\.\\d*)?|\\.\\d+))i$");
+
+    public static ComplexNumber parse(final String text) {
+        var matcher = CANONICAL_PATTERN.matcher(text);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("ComplexNumber requires algebraic form such as 3+4i or 3-4i");
+        }
+        return new ComplexNumber(new BigDecimal(matcher.group(1)), new BigDecimal(matcher.group(2)));
+    }
+
+    public String canonical() {
+        var imaginaryText = imaginary.toPlainString();
+        return real.toPlainString()
+                + (imaginary.signum() >= 0 ? "+" : "")
+                + imaginaryText
+                + "i";
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberTest.java
new file mode 100644
index 00000000000..7c1269a6871
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberTest.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
+
+import java.math.BigDecimal;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ComplexNumberTest {
+
+    @Test
+    void canonicalFormUsesAlgebraicNotation() {
+        assertEquals("3+4i", new ComplexNumber(new BigDecimal("3"), new BigDecimal("4")).canonical());
+        assertEquals(
+                "1.25-2.500i",
+                new ComplexNumber(new BigDecimal("1.25"), new BigDecimal("-2.500")).canonical());
+    }
+
+    @Test
+    void algebraicFormRoundTripsAndLegacyCommaFormIsRejected() {
+        assertEquals(
+                new ComplexNumber(new BigDecimal("3"), new BigDecimal("4")),
+                ComplexNumber.parse("3+4i"));
+        assertEquals(
+                new ComplexNumber(new BigDecimal("1.25"), new BigDecimal("-2.500")),
+                ComplexNumber.parse("1.25-2.500i"));
+        assertThrows(IllegalArgumentException.class, () -> ComplexNumber.parse("3,4"));
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberValueSemantics.java
similarity index 51%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
copy to viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberValueSemantics.java
index bb7efbcacfd..67e4bc6b258 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ComplexNumberValueSemantics.java
@@ -11,24 +11,32 @@
  *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.graphql.model.types;
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
 
-import graphql.schema.GraphQLScalarType;
+import org.springframework.stereotype.Component;
 
-/**
- * Internal SPI.
- */
-public interface ScalarMapper {
+import org.apache.causeway.applib.value.semantics.ValueSemanticsBasedOnIdStringifierEntityAgnostic;
+
+@Component
+public class ComplexNumberValueSemantics
+        extends ValueSemanticsBasedOnIdStringifierEntityAgnostic<ComplexNumber> {
 
-    GraphQLScalarType scalarTypeFor(final Class<?> clazz);
+    public ComplexNumberValueSemantics() {
+        super(ComplexNumber.class);
+    }
 
-    Object unmarshal(
-            final Object argumentValue,
-            final Class<?> targetType);
+    @Override
+    public String enstring(final ComplexNumber value) {
+        return value.canonical();
+    }
 
+    @Override
+    public ComplexNumber destring(final String stringified) {
+        return ComplexNumber.parse(stringified);
+    }
 }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ScalarMarshallerComplexNumber.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ScalarMarshallerComplexNumber.java
new file mode 100644
index 00000000000..c6a5d1e4f43
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/ScalarMarshallerComplexNumber.java
@@ -0,0 +1,105 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
+
+import java.util.Locale;
+
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.viewer.graphql.applib.marshallers.ScalarMarshallerAbstract;
+
+import graphql.GraphQLContext;
+import graphql.execution.CoercedVariables;
+import graphql.language.StringValue;
+import graphql.language.Value;
+import graphql.schema.Coercing;
+import graphql.schema.CoercingParseLiteralException;
+import graphql.schema.CoercingParseValueException;
+import graphql.schema.CoercingSerializeException;
+import graphql.schema.GraphQLScalarType;
+
+@Component
+@Priority(PriorityPrecedence.EARLY)
+public class ScalarMarshallerComplexNumber extends ScalarMarshallerAbstract<ComplexNumber> {
+
+    private static final GraphQLScalarType SCALAR = GraphQLScalarType.newScalar()
+            .name("ComplexNumber")
+            .description("Application-defined reversible complex number in algebraic form, such as 3+4i or 3-4i.")
+            .coercing(new Coercing<ComplexNumber, String>() {
+                @Override
+                public String serialize(
+                        final Object dataFetcherResult,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    if (dataFetcherResult instanceof ComplexNumber complexNumber) {
+                        return complexNumber.canonical();
+                    }
+                    throw new CoercingSerializeException("Expected ComplexNumber output");
+                }
+
+                @Override
+                public ComplexNumber parseValue(
+                        final Object input,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    if (!(input instanceof String text)) {
+                        throw new CoercingParseValueException("Expected ComplexNumber input as a string");
+                    }
+                    try {
+                        return ComplexNumber.parse(text);
+                    } catch (RuntimeException ex) {
+                        throw new CoercingParseValueException("Invalid ComplexNumber value");
+                    }
+                }
+
+                @Override
+                public ComplexNumber parseLiteral(
+                        final Value<?> input,
+                        final CoercedVariables variables,
+                        final GraphQLContext graphQLContext,
+                        final Locale locale) {
+                    if (!(input instanceof StringValue stringValue)) {
+                        throw new CoercingParseLiteralException("Expected ComplexNumber input as a string literal");
+                    }
+                    try {
+                        return ComplexNumber.parse(stringValue.getValue());
+                    } catch (RuntimeException ex) {
+                        throw new CoercingParseLiteralException("Invalid ComplexNumber value");
+                    }
+                }
+            })
+            .build();
+
+    @Inject
+    public ScalarMarshallerComplexNumber(final CausewayConfiguration causewayConfiguration) {
+        super(ComplexNumber.class, SCALAR, causewayConfiguration, true);
+    }
+
+    @Override
+    public ComplexNumber unmarshal(final Object graphValue, final Class<?> targetType) {
+        return graphValue instanceof ComplexNumber complexNumber
+                ? complexNumber
+                : ComplexNumber.parse((String) graphValue);
+    }
+}
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValue.java
similarity index 72%
copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
copy to viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValue.java
index bb7efbcacfd..a33081956d1 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/ScalarMapper.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValue.java
@@ -16,19 +16,18 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.viewer.graphql.model.types;
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
 
-import graphql.schema.GraphQLScalarType;
+import jakarta.inject.Named;
 
-/**
- * Internal SPI.
- */
-public interface ScalarMapper {
-
-    GraphQLScalarType scalarTypeFor(final Class<?> clazz);
+import org.apache.causeway.applib.annotation.Value;
 
-    Object unmarshal(
-            final Object argumentValue,
-            final Class<?> targetType);
+@Named("university.calc.UnmappedValue")
+@Value
+public record UnmappedValue(String value) {
 
+    @Override
+    public String toString() {
+        return value;
+    }
 }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValueSemantics.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValueSemantics.java
new file mode 100644
index 00000000000..64e734c15a8
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/calc/UnmappedValueSemantics.java
@@ -0,0 +1,52 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.domain.calc;
+
+import jakarta.inject.Named;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.causeway.applib.value.semantics.ValueDecomposition;
+import org.apache.causeway.applib.value.semantics.ValueSemanticsAbstract;
+import org.apache.causeway.schema.common.v2.ValueType;
+
+@Component
+@Named("university.calc.UnmappedValueSemantics")
+public class UnmappedValueSemantics extends ValueSemanticsAbstract<UnmappedValue> {
+
+    @Override
+    public Class<UnmappedValue> getCorrespondingClass() {
+        return UnmappedValue.class;
+    }
+
+    @Override
+    public ValueType getSchemaValueType() {
+        return ValueType.STRING;
+    }
+
+    @Override
+    public ValueDecomposition decompose(final UnmappedValue value) {
+        return decomposeAsString(value, UnmappedValue::value, () -> null);
+    }
+
+    @Override
+    public UnmappedValue compose(final ValueDecomposition decomposition) {
+        return composeFromString(decomposition, UnmappedValue::new, () -> null);
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
index 2bb155dd7af..4fb253e54e7 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
@@ -31,6 +31,7 @@
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
+import jakarta.persistence.Transient;
 
 import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.applib.annotation.DomainObjectLayout;
@@ -100,7 +101,10 @@ public String validateName(final String proposedName) {
     @Embedded
     private BlobJpaEmbeddable photo;
 
-    @Property(optionality = Optionality.OPTIONAL)
+    @Property(
+            optionality = Optionality.OPTIONAL,
+            editing = Editing.ENABLED,
+            fileAccept = "application/pdf")
     @PropertyLayout(fieldSetId = "content", sequence = "1")
     public Blob getPhoto() {
         return BlobJpaEmbeddable.toBlob(photo);
@@ -109,10 +113,18 @@ public void setPhoto(final Blob photo) {
         this.photo = BlobJpaEmbeddable.fromBlob(photo);
     }
 
-    @Property
+    @Transient
+    private Clob profile;
+
+    @Property(editing = Editing.ENABLED, fileAccept = "text/plain")
     @PropertyLayout(fieldSetId = "content", sequence = "2")
     public Clob getProfile() {
-        return new Clob("profile.txt", "text/plain", "Profile for " + name);
+        return profile != null
+                ? profile
+                : new Clob("profile.txt", "text/plain", "Profile for " + name);
+    }
+    public void setProfile(final Clob profile) {
+        this.profile = profile;
     }
 
     @Property
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics._.gql
new file mode 100644
index 00000000000..ae61a6d219e
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics._.gql
@@ -0,0 +1,32 @@
+{
+  rich {
+    university_calc_Calculator {
+      echoJavaSqlTime {
+        invoke(value: "12:34:56") { results }
+      }
+      echoLocale {
+        invoke(value: "en-GB") { results }
+      }
+      echoBookmark {
+        invoke(value: "university.dept.StaffMember:1") { results }
+      }
+      echoApplicationFeatureId {
+        invoke(value: "VFlQRTp1bml2ZXJzaXR5LmNhbGMuQ2FsY3VsYXRvcg==") { results }
+      }
+      echoPassword {
+        invoke(value: "PRIVATE_PASSWORD_INPUT") { results }
+      }
+      sampleMarkup {
+        invoke { results }
+      }
+      echoLocalResourcePath {
+        invoke(value: { path: "docs/index.html", openUrlStrategy: SAME_WINDOW }) {
+          results { path openUrlStrategy }
+        }
+      }
+      sampleUnmappedValue {
+        invoke { results }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics.approved.json
new file mode 100644
index 00000000000..43239eac5da
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.built_in_value_semantics.approved.json
@@ -0,0 +1,51 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "echoJavaSqlTime" : {
+          "invoke" : {
+            "results" : "12:34:56"
+          }
+        },
+        "echoLocale" : {
+          "invoke" : {
+            "results" : "en-GB"
+          }
+        },
+        "echoBookmark" : {
+          "invoke" : {
+            "results" : "university.dept.StaffMember:1"
+          }
+        },
+        "echoApplicationFeatureId" : {
+          "invoke" : {
+            "results" : "VFlQRTp1bml2ZXJzaXR5LmNhbGMuQ2FsY3VsYXRvcg=="
+          }
+        },
+        "echoPassword" : {
+          "invoke" : {
+            "results" : "suppressed"
+          }
+        },
+        "sampleMarkup" : {
+          "invoke" : {
+            "results" : "<strong>GraphQL markup</strong>"
+          }
+        },
+        "echoLocalResourcePath" : {
+          "invoke" : {
+            "results" : {
+              "path" : "docs/index.html",
+              "openUrlStrategy" : "SAME_WINDOW"
+            }
+          }
+        },
+        "sampleUnmappedValue" : {
+          "invoke" : {
+            "results" : "[unsupported]"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins._.gql
new file mode 100644
index 00000000000..b53a1b42457
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins._.gql
@@ -0,0 +1,19 @@
+{
+  sqlTime: __type(name: "SqlTime") { kind name description }
+  locale: __type(name: "Locale") { kind name description }
+  bookmark: __type(name: "Bookmark") { kind name description }
+  applicationFeatureId: __type(name: "ApplicationFeatureId") { kind name description }
+  password: __type(name: "Password") { kind name description }
+  markup: __type(name: "Markup") { kind name description }
+  localResourcePathInput: __type(name: "LocalResourcePathInput") {
+    kind
+    name
+    inputFields { name type { kind name ofType { kind name } } }
+  }
+  localResourcePathValue: __type(name: "LocalResourcePathValue") {
+    kind
+    name
+    fields { name type { kind name ofType { kind name } } }
+  }
+  unsupported: __type(name: "UnsupportedValue") { kind name description }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins.approved.json
new file mode 100644
index 00000000000..bdb671d8ab1
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_fail_closed_built_ins.approved.json
@@ -0,0 +1,89 @@
+{
+  "data" : {
+    "sqlTime" : {
+      "kind" : "SCALAR",
+      "name" : "SqlTime",
+      "description" : "An ISO-8601 time-only value at second precision used for java.sql.Time."
+    },
+    "locale" : {
+      "kind" : "SCALAR",
+      "name" : "Locale",
+      "description" : "An IETF BCP 47 language tag used for java.util.Locale."
+    },
+    "bookmark" : {
+      "kind" : "SCALAR",
+      "name" : "Bookmark",
+      "description" : "A Causeway bookmark in logical-type:identifier form."
+    },
+    "applicationFeatureId" : {
+      "kind" : "SCALAR",
+      "name" : "ApplicationFeatureId",
+      "description" : "A URL-safe encoded Causeway application feature identifier."
+    },
+    "password" : {
+      "kind" : "SCALAR",
+      "name" : "Password",
+      "description" : "A protected Causeway password value. Input constructs the value; output is always suppressed."
+    },
+    "markup" : {
+      "kind" : "SCALAR",
+      "name" : "Markup",
+      "description" : "Causeway markup HTML. This scalar is output-only and clients must treat the content as markup."
+    },
+    "localResourcePathInput" : {
+      "kind" : "INPUT_OBJECT",
+      "name" : "LocalResourcePathInput",
+      "inputFields" : [ {
+        "name" : "path",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "SCALAR",
+            "name" : "String"
+          }
+        }
+      }, {
+        "name" : "openUrlStrategy",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "ENUM",
+            "name" : "OpenUrlStrategy"
+          }
+        }
+      } ]
+    },
+    "localResourcePathValue" : {
+      "kind" : "OBJECT",
+      "name" : "LocalResourcePathValue",
+      "fields" : [ {
+        "name" : "path",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "SCALAR",
+            "name" : "String"
+          }
+        }
+      }, {
+        "name" : "openUrlStrategy",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "ENUM",
+            "name" : "OpenUrlStrategy"
+          }
+        }
+      } ]
+    },
+    "unsupported" : {
+      "kind" : "SCALAR",
+      "name" : "UnsupportedValue",
+      "description" : "Unsupported Causeway value. Strict output is redacted and input requires an explicit reversible ScalarMarshaller or TypeMapper."
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input._.gql
new file mode 100644
index 00000000000..2bd626978fe
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input._.gql
@@ -0,0 +1,19 @@
+{
+  __type(name: "BlobInput") {
+    kind
+    name
+    description
+    inputFields {
+      name
+      description
+      type {
+        kind
+        name
+        ofType {
+          kind
+          name
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input.approved.json
new file mode 100644
index 00000000000..f350f1035bf
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_resource_input.approved.json
@@ -0,0 +1,43 @@
+{
+  "data" : {
+    "__type" : {
+      "kind" : "INPUT_OBJECT",
+      "name" : "BlobInput",
+      "description" : "Bounded inline Causeway Blob input.",
+      "inputFields" : [ {
+        "name" : "name",
+        "description" : "Resource filename.",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "SCALAR",
+            "name" : "String"
+          }
+        }
+      }, {
+        "name" : "mimeType",
+        "description" : "IANA media type accepted by the target member.",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "SCALAR",
+            "name" : "String"
+          }
+        }
+      }, {
+        "name" : "base64",
+        "description" : "RFC 4648 base64 content, bounded after decoding.",
+        "type" : {
+          "kind" : "NON_NULL",
+          "name" : null,
+          "ofType" : {
+            "kind" : "SCALAR",
+            "name" : "String"
+          }
+        }
+      } ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input._.gql
new file mode 100644
index 00000000000..211fde3deb2
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input._.gql
@@ -0,0 +1,7 @@
+{
+  __type(name: "UnsupportedValue") {
+    kind
+    name
+    description
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input.approved.json
new file mode 100644
index 00000000000..aa972fee906
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_unsupported_input.approved.json
@@ -0,0 +1,9 @@
+{
+  "data" : {
+    "__type" : {
+      "kind" : "SCALAR",
+      "name" : "UnsupportedValue",
+      "description" : "Unsupported Causeway value. Strict output is redacted and input requires an explicit reversible ScalarMarshaller or TypeMapper."
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar._.gql
new file mode 100644
index 00000000000..887ea48aca4
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar._.gql
@@ -0,0 +1,7 @@
+{
+  __type(name: "LocalDateTime") {
+    kind
+    name
+    description
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar.approved.json
new file mode 100644
index 00000000000..4a41532d557
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.introspects_value_scalar.approved.json
@@ -0,0 +1,9 @@
+{
+  "data" : {
+    "__type" : {
+      "kind" : "SCALAR",
+      "name" : "LocalDateTime",
+      "description" : "An ISO-8601 local date-time without an offset or zone; fractional seconds are preserved."
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected._.gql
new file mode 100644
index 00000000000..64627c7de31
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      echoLocalDateTime {
+        invoke(value: "not-a-date") {
+          results
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected.approved.json
new file mode 100644
index 00000000000..82c41c533f5
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.malformed_value_is_rejected.approved.json
@@ -0,0 +1,12 @@
+{
+  "errors" : [ {
+    "message" : "Validation error (WrongType@[rich/university_calc_Calculator/echoLocalDateTime/invoke]) : argument 'value' with value 'StringValue{value='not-a-date'}' is not a valid 'LocalDateTime' - Invalid LocalDateTime value",
+    "locations" : [ {
+      "column" : 16,
+      "line" : 5
+    } ],
+    "extensions" : {
+      "classification" : "ValidationError"
+    }
+  } ]
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected._.gql
new file mode 100644
index 00000000000..283738bc5e0
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected._.gql
@@ -0,0 +1,13 @@
+{
+  rich {
+    university_calc_Calculator {
+      echoBlob {
+        invoke(value: {name: "private.pdf", mimeType: "application/pdf", base64: "U0VOU0lUSVZFX0JMT0JfQ09OVEVOVA=="}) {
+          results {
+            name
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected.approved.json
new file mode 100644
index 00000000000..eddb82a0f10
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.resource_media_type_is_rejected.approved.json
@@ -0,0 +1,24 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/rich/university_calc_Calculator/echoBlob/invoke/results) : Resource media type is not accepted for member 'org.apache.causeway.viewer.graphql.viewer.test.domain.calc.Calculator#echoBlob[0]'",
+    "locations" : [ {
+      "column" : 11,
+      "line" : 6
+    } ],
+    "path" : [ "rich", "university_calc_Calculator", "echoBlob", "invoke", "results" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "echoBlob" : {
+          "invoke" : {
+            "results" : null
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
index 7ff2e97cc7b..05c1e03808f 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
@@ -4,7 +4,7 @@
       "university_calc_Calculator" : {
         "someLocale" : {
           "invoke" : {
-            "results" : "en_GB"
+            "results" : "en-GB"
           }
         }
       }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected._.gql
new file mode 100644
index 00000000000..eacd6e43f36
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      echoUnmappedValue {
+        invoke(value: "unsupported") {
+          results
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected.approved.json
new file mode 100644
index 00000000000..87ee34cbc2e
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.unsupported_value_input_is_rejected.approved.json
@@ -0,0 +1,12 @@
+{
+  "errors" : [ {
+    "message" : "Validation error (WrongType@[rich/university_calc_Calculator/echoUnmappedValue/invoke]) : argument 'value' with value 'StringValue{value='unsupported'}' is not a valid 'UnsupportedValue' - This Causeway value has no reversible GraphQL input strategy; register a ScalarMarshaller or TypeMapper",
+    "locations" : [ {
+      "column" : 16,
+      "line" : 5
+    } ],
+    "extensions" : {
+      "classification" : "ValidationError"
+    }
+  } ]
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips._.gql
new file mode 100644
index 00000000000..5cdd29fb367
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips._.gql
@@ -0,0 +1,132 @@
+{
+  rich {
+    university_calc_Calculator {
+      echoLocalDateTime {
+        invoke(value: "2026-08-18T09:10:11.123456789") {
+          args { value }
+          results
+        }
+      }
+      optionalLocalDateTime {
+        invoke {
+          args { value }
+          results
+        }
+      }
+      echoUrl {
+        invoke(value: "https://causeway.apache.org/docs/index.html?x=1") {
+          args { value }
+          results
+        }
+      }
+      echoJavaUtilDate {
+        invoke(value: "2026-08-18T08:10:11.123Z") {
+          args { value }
+          results
+        }
+      }
+      echoJavaSqlDate {
+        invoke(value: "2026-08-18") {
+          args { value }
+          results
+        }
+      }
+      echoJavaSqlTimestamp {
+        invoke(value: "2026-08-18T09:10:11.123456789") {
+          args { value }
+          results
+        }
+      }
+      positiveComplexNumber: echoComplexNumber {
+        invoke(value: "3+4i") {
+          args { value }
+          results
+        }
+      }
+      negativeComplexNumber: echoComplexNumber {
+        invoke(value: "1.25-2.500i") {
+          args { value }
+          results
+        }
+      }
+      echoBlob {
+        params {
+          value {
+            datatype
+            fileAccept
+            inlineInputMaxBytes
+            resourceInputMode
+          }
+        }
+        invoke(value: {name: "hello.txt", mimeType: "text/plain", base64: "aGVsbG8="}) {
+          args {
+            value {
+              name
+              mimeType
+              byteLength
+              transferMode
+              inlineOutputMaxBytes
+              base64
+            }
+          }
+          results {
+            name
+            mimeType
+            byteLength
+            transferMode
+            inlineOutputMaxBytes
+            base64
+          }
+        }
+      }
+      optionalBlob {
+        invoke {
+          args { value { name } }
+          results { name }
+        }
+      }
+      echoClob {
+        params {
+          value {
+            datatype
+            fileAccept
+            inlineInputMaxBytes
+            resourceInputMode
+          }
+        }
+        invoke(value: {name: "hello.txt", mimeType: "text/plain", chars: "héllo"}) {
+          args {
+            value {
+              name
+              mimeType
+              byteLength
+              characterLength
+              transferMode
+              inlineOutputMaxBytes
+              chars
+            }
+          }
+          results {
+            name
+            mimeType
+            byteLength
+            characterLength
+            transferMode
+            inlineOutputMaxBytes
+            chars
+          }
+        }
+      }
+      someLocale {
+        invoke {
+          results
+        }
+      }
+      secretPassword {
+        invoke {
+          results
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips.approved.json
new file mode 100644
index 00000000000..92db530ff99
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/calc/Calculator_IntegTest.each.value_and_resource_round_trips.approved.json
@@ -0,0 +1,152 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "echoLocalDateTime" : {
+          "invoke" : {
+            "args" : {
+              "value" : "2026-08-18T09:10:11.123456789"
+            },
+            "results" : "2026-08-18T09:10:11.123456789"
+          }
+        },
+        "optionalLocalDateTime" : {
+          "invoke" : {
+            "args" : {
+              "value" : null
+            },
+            "results" : null
+          }
+        },
+        "echoUrl" : {
+          "invoke" : {
+            "args" : {
+              "value" : "https://causeway.apache.org/docs/index.html?x=1"
+            },
+            "results" : "https://causeway.apache.org/docs/index.html?x=1"
+          }
+        },
+        "echoJavaUtilDate" : {
+          "invoke" : {
+            "args" : {
+              "value" : "2026-08-18T08:10:11.123Z"
+            },
+            "results" : "2026-08-18T08:10:11.123Z"
+          }
+        },
+        "echoJavaSqlDate" : {
+          "invoke" : {
+            "args" : {
+              "value" : "2026-08-18"
+            },
+            "results" : "2026-08-18"
+          }
+        },
+        "echoJavaSqlTimestamp" : {
+          "invoke" : {
+            "args" : {
+              "value" : "2026-08-18T09:10:11.123456789"
+            },
+            "results" : "2026-08-18T09:10:11.123456789"
+          }
+        },
+        "positiveComplexNumber" : {
+          "invoke" : {
+            "args" : {
+              "value" : "3+4i"
+            },
+            "results" : "3+4i"
+          }
+        },
+        "negativeComplexNumber" : {
+          "invoke" : {
+            "args" : {
+              "value" : "1.25-2.500i"
+            },
+            "results" : "1.25-2.500i"
+          }
+        },
+        "echoBlob" : {
+          "params" : {
+            "value" : {
+              "datatype" : "rich__causeway_applib_value_Blob",
+              "fileAccept" : "text/plain",
+              "inlineInputMaxBytes" : 1048576,
+              "resourceInputMode" : "STRUCTURED_INLINE"
+            }
+          },
+          "invoke" : {
+            "args" : {
+              "value" : {
+                "name" : "hello.txt",
+                "mimeType" : "text/plain",
+                "byteLength" : 5,
+                "transferMode" : "INLINE",
+                "inlineOutputMaxBytes" : 1048576,
+                "base64" : "aGVsbG8="
+              }
+            },
+            "results" : {
+              "name" : "hello.txt",
+              "mimeType" : "text/plain",
+              "byteLength" : 5,
+              "transferMode" : "INLINE",
+              "inlineOutputMaxBytes" : 1048576,
+              "base64" : "aGVsbG8="
+            }
+          }
+        },
+        "optionalBlob" : {
+          "invoke" : {
+            "args" : {
+              "value" : null
+            },
+            "results" : null
+          }
+        },
+        "echoClob" : {
+          "params" : {
+            "value" : {
+              "datatype" : "rich__causeway_applib_value_Clob",
+              "fileAccept" : "text/plain",
+              "inlineInputMaxBytes" : 1048576,
+              "resourceInputMode" : "STRUCTURED_INLINE"
+            }
+          },
+          "invoke" : {
+            "args" : {
+              "value" : {
+                "name" : "hello.txt",
+                "mimeType" : "text/plain",
+                "byteLength" : 6,
+                "characterLength" : 5,
+                "transferMode" : "INLINE",
+                "inlineOutputMaxBytes" : 1048576,
+                "chars" : "héllo"
+              }
+            },
+            "results" : {
+              "name" : "hello.txt",
+              "mimeType" : "text/plain",
+              "byteLength" : 6,
+              "characterLength" : 5,
+              "transferMode" : "INLINE",
+              "inlineOutputMaxBytes" : 1048576,
+              "chars" : "héllo"
+            }
+          }
+        },
+        "someLocale" : {
+          "invoke" : {
+            "results" : "en-GB"
+          }
+        },
+        "secretPassword" : {
+          "invoke" : {
+            "results" : "suppressed"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/BuiltInValueTypeClassifications_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/BuiltInValueTypeClassifications_IntegTest.java
new file mode 100644
index 00000000000..16c6fe34042
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/BuiltInValueTypeClassifications_IntegTest.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e.special;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
+import org.apache.causeway.viewer.graphql.model.types.BuiltInValueTypeClassifications;
+import org.apache.causeway.viewer.graphql.model.types.TypeMapper;
+import org.apache.causeway.viewer.graphql.viewer.test.e2e.Abstract_IntegTest;
+
+import graphql.schema.GraphQLNamedType;
+import graphql.schema.GraphQLType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Order(64)
+public class BuiltInValueTypeClassifications_IntegTest extends Abstract_IntegTest {
+
+    @Inject
+    private List<ValueSemanticsProvider<?>> valueSemanticsProviders;
+
+    @Inject
+    private TypeMapper typeMapper;
+
+    @Test
+    void everyFrameworkValueSemanticsHasAnExplicitGraphQlClassification() {
+        Set<String> frameworkValueTypes = valueSemanticsProviders.stream()
+                .filter(provider -> provider.getClass().getName()
+                        .startsWith("org.apache.causeway.core.metamodel.valuesemantics."))
+                .map(ValueSemanticsProvider::getCorrespondingClass)
+                .map(Class::getName)
+                .collect(Collectors.toSet());
+
+        assertThat(BuiltInValueTypeClassifications.classNames())
+                .containsExactlyInAnyOrderElementsOf(frameworkValueTypes);
+    }
+
+    @Test
+    void everyClassificationMatchesItsGeneratedGraphQlCapability() throws Exception {
+        for (var className : BuiltInValueTypeClassifications.classNames()) {
+            var javaType = Class.forName(className);
+            var classification = BuiltInValueTypeClassifications.classificationFor(javaType).orElseThrow();
+            var inputName = typeName(typeMapper.inputTypeFor(javaType));
+            var outputName = typeName(typeMapper.outputTypeFor(javaType));
+
+            switch (classification) {
+                case REVERSIBLE, PROTECTED, STRUCTURED -> {
+                    assertThat(inputName).as(className + " input").isNotEqualTo("UnsupportedValue");
+                    assertThat(outputName).as(className + " output").isNotEqualTo("UnsupportedValue");
+                }
+                case OUTPUT_ONLY -> {
+                    assertThat(inputName).as(className + " input").isEqualTo("UnsupportedValue");
+                    assertThat(outputName).as(className + " output").isNotEqualTo("UnsupportedValue");
+                }
+                case UNSUPPORTED -> {
+                    assertThat(inputName).as(className + " input").isEqualTo("UnsupportedValue");
+                    assertThat(outputName).as(className + " output").isEqualTo("UnsupportedValue");
+                }
+            }
+        }
+    }
+
+    private static String typeName(final GraphQLType type) {
+        return ((GraphQLNamedType) type).getName();
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/LegacyUnsupportedOutputPolicy_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/LegacyUnsupportedOutputPolicy_IntegTest.java
new file mode 100644
index 00000000000..275be1eb65b
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/LegacyUnsupportedOutputPolicy_IntegTest.java
@@ -0,0 +1,102 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e.special;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Map;
+
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.viewer.graphql.viewer.test.e2e.Abstract_IntegTest;
+
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Order(63)
+@ActiveProfiles("test")
+public class LegacyUnsupportedOutputPolicy_IntegTest extends Abstract_IntegTest {
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @DynamicPropertySource
+    static void legacyUnsupportedOutput(final DynamicPropertyRegistry registry) {
+        registry.add(
+                "causeway.viewer.graphql.values.unsupported-output-policy",
+                CausewayConfiguration.Viewer.Graphql.Values.UnsupportedOutputPolicy.LEGACY_STRING::name);
+    }
+
+    @Test
+    void legacyStringOutputIsExplicitAndDoesNotEnableInput() throws Exception {
+        var output = executeGraphQl("""
+                {
+                  rich {
+                    university_calc_Calculator {
+                      sampleUnmappedValue { invoke { results } }
+                    }
+                  }
+                }
+                """, Map.of());
+        assertThat(output.at("/data/rich/university_calc_Calculator/sampleUnmappedValue/invoke/results")
+                .stringValue()).isEqualTo("NEVER_DISCLOSE_UNMAPPED_VALUE");
+
+        var input = executeGraphQl("""
+                query Unsupported($value: UnsupportedValue!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoUnmappedValue { invoke(value: $value) { results } }
+                    }
+                  }
+                }
+                """, Map.of("value", "PRIVATE_LEGACY_INPUT"));
+        assertThat(input.at("/errors/0/message").stringValue())
+                .contains("no reversible GraphQL input strategy")
+                .doesNotContain("PRIVATE_LEGACY_INPUT");
+    }
+
+    private JsonNode executeGraphQl(
+            final String query,
+            final Map<String, ?> variables) throws Exception {
+        var requestBody = objectMapper.writeValueAsString(Map.of(
+                "query", query,
+                "variables", variables));
+        var request = HttpRequest.newBuilder()
+                .uri(URI.create(String.format("http://0.0.0.0:%d/graphql", port)))
+                .header("Content-Type", "application/json")
+                .timeout(Duration.ofSeconds(30))
+                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
+                .build();
+        var response = HttpClient.newHttpClient().send(
+                request,
+                HttpResponse.BodyHandlers.ofString());
+        assertThat(response.statusCode()).isEqualTo(200);
+        return objectMapper.readTree(response.body());
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourcePolicy_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourcePolicy_IntegTest.java
index 1d3274f0ee9..f2843882be7 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourcePolicy_IntegTest.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourcePolicy_IntegTest.java
@@ -26,6 +26,7 @@
 import java.util.Map;
 
 import graphql.schema.GraphQLObjectType;
+import graphql.schema.GraphQLTypeUtil;
 import tools.jackson.databind.JsonNode;
 import tools.jackson.databind.ObjectMapper;
 
@@ -92,6 +93,30 @@ void separatesStructuralMetadataFromValueContent() throws Exception {
         assertThat(richLobType.getFieldDefinition("bytes")).isNull();
         assertThat(simpleLobType.getFieldDefinition("bytes")).isNull();
 
+        var photoProperty = (GraphQLObjectType) schema.getType(
+                "rich__university_dept_StaffMember__photo__gqlv_property");
+        assertThat(GraphQLTypeUtil.simplePrint(
+                photoProperty.getFieldDefinition("set").getArgument("photo").getType()))
+                .isEqualTo("UnsupportedValue");
+
+        var echoBlobAction = (GraphQLObjectType) schema.getType(
+                "rich__university_calc_Calculator__echoBlob__gqlv_action");
+        assertThat(GraphQLTypeUtil.simplePrint(
+                echoBlobAction.getFieldDefinition("invoke").getArgument("value").getType()))
+                .isEqualTo("UnsupportedValue!");
+        var parameterMetadata = executeGraphQl(objectMapper, """
+                {
+                  rich {
+                    university_calc_Calculator {
+                      echoBlob { params { value { resourceInputMode } } }
+                    }
+                  }
+                }
+                """);
+        assertThat(parameterMetadata.at(
+                "/data/rich/university_calc_Calculator/echoBlob/params/value/resourceInputMode").stringValue())
+                .isEqualTo("FORBIDDEN");
+
         var gridResponse = get(gridUrl);
         assertThat(gridResponse.statusCode()).isEqualTo(200);
         assertThat(gridResponse.headers().firstValue("Content-Type").orElseThrow())
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourceValuePolicy_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourceValuePolicy_IntegTest.java
new file mode 100644
index 00000000000..5dbe899c0d1
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/ResourceValuePolicy_IntegTest.java
@@ -0,0 +1,228 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e.special;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Map;
+
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.viewer.graphql.viewer.test.e2e.Abstract_IntegTest;
+
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Order(62)
+@ActiveProfiles("test")
+@ExtendWith(OutputCaptureExtension.class)
+public class ResourceValuePolicy_IntegTest extends Abstract_IntegTest {
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @DynamicPropertySource
+    static void resourceValuePolicy(final DynamicPropertyRegistry registry) {
+        registry.add(
+                "causeway.viewer.graphql.resources.value-content-response-type",
+                CausewayConfiguration.Viewer.Graphql.ResponseType.DIRECT::name);
+        registry.add("causeway.viewer.graphql.resources.inline-input-max-bytes", () -> 8);
+        registry.add("causeway.viewer.graphql.resources.inline-output-max-bytes", () -> 8);
+    }
+
+    @Test
+    void oversizedActionResultsRemainMetadataOnly() throws Exception {
+        var response = executeGraphQl("""
+                {
+                  rich {
+                    university_calc_Calculator {
+                      sampleBlob { invoke { results { name byteLength transferMode inlineOutputMaxBytes base64 } } }
+                      sampleClob { invoke { results { name byteLength transferMode inlineOutputMaxBytes chars } } }
+                    }
+                  }
+                }
+                """, Map.of());
+
+        assertThat(response.at("/errors").isMissingNode()).isTrue();
+        var calculator = response.at("/data/rich/university_calc_Calculator");
+        assertThat(calculator.at("/sampleBlob/invoke/results/byteLength").intValue()).isEqualTo(11);
+        assertThat(calculator.at("/sampleBlob/invoke/results/transferMode").stringValue())
+                .isEqualTo("METADATA_ONLY");
+        assertThat(calculator.at("/sampleBlob/invoke/results/inlineOutputMaxBytes").intValue()).isEqualTo(8);
+        assertThat(calculator.at("/sampleBlob/invoke/results/base64").isNull()).isTrue();
+        assertThat(calculator.at("/sampleClob/invoke/results/transferMode").stringValue())
+                .isEqualTo("METADATA_ONLY");
+        assertThat(calculator.at("/sampleClob/invoke/results/chars").isNull()).isTrue();
+    }
+
+    @Test
+    void oversizedAndMalformedInputsAreRejectedWithoutContentDisclosure(
+            final CapturedOutput output) throws Exception {
+        var oversizedSecret = "PRIVATE_RESOURCE_CONTENT";
+        var oversized = executeGraphQl("""
+                query Oversized($value: ClobInput!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoClob { invoke(value: $value) { results { name } } }
+                    }
+                  }
+                }
+                """, Map.of("value", Map.of(
+                        "name", "private.txt",
+                        "mimeType", "text/plain",
+                        "chars", oversizedSecret)));
+        assertThat(oversized.at("/errors/0/message").stringValue())
+                .contains("configured inline byte limit")
+                .doesNotContain(oversizedSecret);
+
+        var malformedSecret = "PRIVATE!";
+        var malformed = executeGraphQl("""
+                query Malformed($value: BlobInput!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoBlob { invoke(value: $value) { results { name } } }
+                    }
+                  }
+                }
+                """, Map.of("value", Map.of(
+                        "name", "private.txt",
+                        "mimeType", "text/plain",
+                        "base64", malformedSecret)));
+        assertThat(malformed.at("/errors/0/message").stringValue())
+                .contains("invalid base64 content")
+                .doesNotContain(malformedSecret);
+        assertThat(output)
+                .doesNotContain(oversizedSecret)
+                .doesNotContain(malformedSecret);
+    }
+
+    @Test
+    void passwordAndUnknownOutputRemainNonDisclosing(
+            final CapturedOutput output) throws Exception {
+        var passwordInput = "PRIVATE_PASSWORD_VARIABLE";
+        var response = executeGraphQl("""
+                query Protected($password: Password!) {
+                  rich {
+                    university_calc_Calculator {
+                      secretPassword { invoke { results } }
+                      echoPassword { invoke(value: $password) { results } }
+                      someLocale { invoke { results } }
+                      sampleUnmappedValue { invoke { results } }
+                    }
+                  }
+                }
+                """, Map.of("password", passwordInput));
+
+        var calculator = response.at("/data/rich/university_calc_Calculator");
+        assertThat(calculator.at("/secretPassword/invoke/results").stringValue()).isEqualTo("suppressed");
+        assertThat(calculator.at("/echoPassword/invoke/results").stringValue()).isEqualTo("suppressed");
+        assertThat(calculator.at("/someLocale/invoke/results").stringValue()).isEqualTo("en-GB");
+        assertThat(calculator.at("/sampleUnmappedValue/invoke/results").stringValue()).isEqualTo("[unsupported]");
+        assertThat(response.toString())
+                .doesNotContain("NEVER_DISCLOSE_THIS_PASSWORD")
+                .doesNotContain("NEVER_DISCLOSE_UNMAPPED_VALUE")
+                .doesNotContain(passwordInput);
+        assertThat(output)
+                .doesNotContain("NEVER_DISCLOSE_THIS_PASSWORD")
+                .doesNotContain("NEVER_DISCLOSE_UNMAPPED_VALUE")
+                .doesNotContain(passwordInput);
+    }
+
+    @Test
+    void scalarAndUnsupportedVariableErrorsDoNotEchoValues(
+            final CapturedOutput output) throws Exception {
+        var temporalSecret = "PRIVATE_INVALID_TEMPORAL";
+        var malformedTemporal = executeGraphQl("""
+                query MalformedTemporal($value: LocalDateTime!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoLocalDateTime { invoke(value: $value) { results } }
+                    }
+                  }
+                }
+                """, Map.of("value", temporalSecret));
+        assertThat(malformedTemporal.at("/errors/0/message").stringValue())
+                .contains("Invalid LocalDateTime value")
+                .doesNotContain(temporalSecret);
+
+        var urlSecret = "PRIVATE_INVALID_URL";
+        var malformedUrl = executeGraphQl("""
+                query MalformedUrl($value: Url!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoUrl { invoke(value: $value) { results } }
+                    }
+                  }
+                }
+                """, Map.of("value", urlSecret));
+        assertThat(malformedUrl.at("/errors/0/message").stringValue())
+                .contains("Invalid Url value")
+                .doesNotContain(urlSecret);
+
+        var unsupportedSecret = "PRIVATE_UNSUPPORTED_VALUE";
+        var unsupported = executeGraphQl("""
+                query Unsupported($value: UnsupportedValue!) {
+                  rich {
+                    university_calc_Calculator {
+                      echoUnmappedValue { invoke(value: $value) { results } }
+                    }
+                  }
+                }
+                """, Map.of("value", unsupportedSecret));
+        assertThat(unsupported.at("/errors/0/message").stringValue())
+                .contains("no reversible GraphQL input strategy")
+                .doesNotContain(unsupportedSecret);
+        assertThat(output)
+                .doesNotContain(temporalSecret)
+                .doesNotContain(urlSecret)
+                .doesNotContain(unsupportedSecret);
+    }
+
+    private JsonNode executeGraphQl(
+            final String query,
+            final Map<String, ?> variables) throws Exception {
+        var requestBody = objectMapper.writeValueAsString(Map.of(
+                "query", query,
+                "variables", variables));
+        var request = HttpRequest.newBuilder()
+                .uri(URI.create(String.format("http://0.0.0.0:%d/graphql", port)))
+                .header("Content-Type", "application/json")
+                .timeout(Duration.ofSeconds(30))
+                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
+                .build();
+        var response = HttpClient.newHttpClient().send(
+                request,
+                HttpResponse.BodyHandlers.ofString());
+        assertThat(response.statusCode()).isEqualTo(200);
+        return objectMapper.readTree(response.body());
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent._.gql
new file mode 100644
index 00000000000..1aec2e6e952
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent._.gql
@@ -0,0 +1,19 @@
+{
+  rich {
+    university_dept_Staff {
+      findStaffMemberByName {
+        invoke(name: "Gerry Jones") {
+          results {
+            hiddenPhoto {
+              validate(hiddenPhoto: {
+                name: "private.pdf",
+                mimeType: "application/pdf",
+                base64: "UFJJVkFURV9ISURERU5fUkVTT1VSQ0VfSU5QVVQ="
+              })
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent.approved.json
new file mode 100644
index 00000000000..b0ba31d9efd
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.hiddenResourceValidationDoesNotProcessOrDiscloseContent.approved.json
@@ -0,0 +1,28 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/rich/university_dept_Staff/findStaffMemberByName/invoke/results/hiddenPhoto/validate) : org.apache.causeway.viewer.graphql.viewer.test.domain.dept.StaffMember#hiddenPhoto",
+    "locations" : [ {
+      "column" : 15,
+      "line" : 8
+    } ],
+    "path" : [ "rich", "university_dept_Staff", "findStaffMemberByName", "invoke", "results", "hiddenPhoto", "validate" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "rich" : {
+      "university_dept_Staff" : {
+        "findStaffMemberByName" : {
+          "invoke" : {
+            "results" : {
+              "hiddenPhoto" : {
+                "validate" : null
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.java
index 97b0535d061..8c651911a6f 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Staff_2_IntegTest.java
@@ -100,6 +100,16 @@ void findsStaffMemberAndSafelyDownloadsResources(final CapturedOutput output) th
                 .doesNotContain("diagnostic-secret-id");
     }
 
+    @Test
+    @UseReporter(DiffReporter.class)
+    void hiddenResourceValidationDoesNotProcessOrDiscloseContent(
+            final CapturedOutput output) throws Exception {
+        var response = submit();
+        Approvals.verify(response, jsonOptions());
+        assertThat(response).doesNotContain("PRIVATE_HIDDEN_RESOURCE_INPUT");
+        assertThat(output).doesNotContain("PRIVATE_HIDDEN_RESOURCE_INPUT");
+    }
+
     private void assertNotFoundWithoutDisclosure(final String url) throws Exception {
         var response = submitReturningBytes(url);
         assertThat(response.statusCode()).isEqualTo(404);
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.java
index 161b2062e7e..cbbc8489294 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.java
@@ -41,7 +41,22 @@ public class StaffMutating_IntegTest extends Abstract_IntegTest {
     @Test
     @UseReporter(DiffReporter.class)
     void staff_member_edit_name() throws Exception {
+        Approvals.verify(submitForJohnGartner(), jsonOptions());
+    }
+
+    @Test
+    @UseReporter(DiffReporter.class)
+    void staff_member_edit_resources() throws Exception {
+        Approvals.verify(submitForJohnGartner(), jsonOptions());
+    }
 
+    @Test
+    @UseReporter(DiffReporter.class)
+    void staff_member_rejects_unaccepted_resource() throws Exception {
+        Approvals.verify(submitForJohnGartner(), jsonOptions());
+    }
+
+    private String submitForJohnGartner() throws Exception {
         final Bookmark bookmark =
                 transactionService.callTransactional(
                         Propagation.REQUIRED,
@@ -51,9 +66,6 @@ void staff_member_edit_name() throws Exception {
                         }
                 ).valueAsNonNullElseFail();
 
-        var response = submit(_Maps.unmodifiable("$staffMemberId", bookmark.identifier()));
-
-        // then payload
-        Approvals.verify(response, jsonOptions());
+        return submit(_Maps.unmodifiable("$staffMemberId", bookmark.identifier()));
     }
 }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources._.gql
new file mode 100644
index 00000000000..ccea2d9f749
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources._.gql
@@ -0,0 +1,35 @@
+mutation {
+  photo: university_dept_StaffMember__photo(
+      _target: {id: "$staffMemberId"},
+      photo: {name: "updated.pdf", mimeType: "application/pdf", base64: "JVBERi0xLjQ="}
+  ) {
+    photo {
+      get {
+        name
+        mimeType
+        byteLength
+        fileAccept
+        inlineInputMaxBytes
+        transferMode
+        bytes
+      }
+    }
+  }
+  profile: university_dept_StaffMember__profile(
+      _target: {id: "$staffMemberId"},
+      profile: {name: "updated.txt", mimeType: "text/plain", chars: "Updated profile"}
+  ) {
+    profile {
+      get {
+        name
+        mimeType
+        byteLength
+        characterLength
+        fileAccept
+        inlineInputMaxBytes
+        transferMode
+        chars
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources.approved.json
new file mode 100644
index 00000000000..fafd4177f07
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_edit_resources.approved.json
@@ -0,0 +1,31 @@
+{
+  "data" : {
+    "photo" : {
+      "photo" : {
+        "get" : {
+          "name" : "updated.pdf",
+          "mimeType" : "application/pdf",
+          "byteLength" : 8,
+          "fileAccept" : "application/pdf",
+          "inlineInputMaxBytes" : 1048576,
+          "transferMode" : "RESOURCE_LINK",
+          "bytes" : "/graphql/object/university.dept.StaffMember:NNN/photo/blobBytes"
+        }
+      }
+    },
+    "profile" : {
+      "profile" : {
+        "get" : {
+          "name" : "updated.txt",
+          "mimeType" : "text/plain",
+          "byteLength" : 15,
+          "characterLength" : 15,
+          "fileAccept" : "text/plain",
+          "inlineInputMaxBytes" : 1048576,
+          "transferMode" : "RESOURCE_LINK",
+          "chars" : "/graphql/object/university.dept.StaffMember:NNN/profile/clobChars"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource._.gql
new file mode 100644
index 00000000000..85143b4a939
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource._.gql
@@ -0,0 +1,12 @@
+mutation {
+  university_dept_StaffMember__photo(
+      _target: {id: "$staffMemberId"},
+      photo: {name: "private.png", mimeType: "image/png", base64: "U0VOU0lUSVZFX0NPTlRFTlQ="}
+  ) {
+    photo {
+      get {
+        name
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource.approved.json
new file mode 100644
index 00000000000..e8ef60d9392
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/univ/staffmutating/StaffMutating_IntegTest.staff_member_rejects_unaccepted_resource.approved.json
@@ -0,0 +1,16 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/university_dept_StaffMember__photo) : Resource media type is not accepted for member 'org.apache.causeway.viewer.graphql.viewer.test.domain.dept.StaffMember#photo'",
+    "locations" : [ {
+      "column" : 3,
+      "line" : 2
+    } ],
+    "path" : [ "university_dept_StaffMember__photo" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "university_dept_StaffMember__photo" : null
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/resources/schema.gql b/viewers/graphql/test/src/test/resources/schema.gql
index 00ef680c853..bbb41dd0d78 100644
--- a/viewers/graphql/test/src/test/resources/schema.gql
+++ b/viewers/graphql/test/src/test/resources/schema.gql
@@ -56,13 +56,42 @@ union rich__university_calc_EditableMementoViewModel__gqlv_union = rich__univers
 "Concrete rich object types assignable to university.dept.Person"
 union rich__university_dept_Person__gqlv_union = rich__university_dept_DeptHead | rich__university_dept_StaffMember
 
+"Metadata-first bounded Causeway Blob output."
+type BlobValue {
+  "RFC 4648 base64 content; null when the configured inline-output limit is exceeded."
+  base64: String
+  byteLength: Int!
+  inlineOutputMaxBytes: Int!
+  mimeType: String!
+  name: String!
+  transferMode: String!
+}
+
+"Metadata-first bounded Causeway Clob output."
+type ClobValue {
+  byteLength: Int!
+  characterLength: Int!
+  "Character content; null when the configured UTF-8 inline-output limit is exceeded."
+  chars: String
+  inlineOutputMaxBytes: Int!
+  mimeType: String!
+  name: String!
+  transferMode: String!
+}
+
+"A Causeway local resource path and browser opening strategy."
+type LocalResourcePathValue {
+  openUrlStrategy: OpenUrlStrategy!
+  path: String!
+}
+
 type Mutation {
   causeway_conf_ConfigurationProperty__key(_target: rich__causeway_conf_ConfigurationProperty__gqlv_input, key: String!): rich__causeway_conf_ConfigurationProperty
   causeway_conf_ConfigurationProperty__value(_target: rich__causeway_conf_ConfigurationProperty__gqlv_input, value: String!): rich__causeway_conf_ConfigurationProperty
   causeway_metamodel_MetamodelInspectView__inspect(_target: rich__causeway_metamodel_MetamodelInspectView__gqlv_input, fullyQualifiedClassName: String!): rich__causeway_metamodel_MetamodelInspectView
   causeway_testing_fixtures_FixtureResult__fixtureScriptClassName(_target: rich__causeway_testing_fixtures_FixtureResult__gqlv_input, fixtureScriptClassName: String): rich__causeway_testing_fixtures_FixtureResult
   causeway_testing_fixtures_FixtureResult__key(_target: rich__causeway_testing_fixtures_FixtureResult__gqlv_input, key: String!): rich__causeway_testing_fixtures_FixtureResult
-  causeway_testing_fixtures_FixtureResult__object(_target: rich__causeway_testing_fixtures_FixtureResult__gqlv_input, object: String!): rich__causeway_testing_fixtures_FixtureResult
+  causeway_testing_fixtures_FixtureResult__object(_target: rich__causeway_testing_fixtures_FixtureResult__gqlv_input, object: UnsupportedValue!): rich__causeway_testing_fixtures_FixtureResult
   org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__friendlyName(_target: rich__org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input, friendlyName: String!): rich__org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript
   university_admin_AdminMenu__actionWithDisabledParam(firstParam: String!, secondParam: String!, thirdParameter: String!): String
   university_admin_AdminMenu__actionWithHiddenParam(firstParam: String!, secondParam: String!): String
@@ -86,7 +115,8 @@ type Mutation {
   university_dept_StaffMember__department(_target: rich__university_dept_StaffMember__gqlv_input, department: rich__university_dept_Department__gqlv_input): rich__university_dept_StaffMember
   university_dept_StaffMember__grade(_target: rich__university_dept_StaffMember__gqlv_input, grade: rich__org_apache_causeway_viewer_graphql_viewer_test_domain_dept_Grade__gqlv_enum!): rich__university_dept_StaffMember
   university_dept_StaffMember__name(_target: rich__university_dept_StaffMember__gqlv_input, name: String!): rich__university_dept_StaffMember
-  university_dept_StaffMember__photo(_target: rich__university_dept_StaffMember__gqlv_input, photo: String): rich__university_dept_StaffMember
+  university_dept_StaffMember__photo(_target: rich__university_dept_StaffMember__gqlv_input, photo: BlobInput): rich__university_dept_StaffMember
+  university_dept_StaffMember__profile(_target: rich__university_dept_StaffMember__gqlv_input, profile: ClobInput!): rich__university_dept_StaffMember
   university_dept_Staff__createStaffMember(department: rich__university_dept_Department__gqlv_input!, name: String!): rich__university_dept_StaffMember
 }
 
@@ -384,10 +414,10 @@ type rich__causeway_applib_UserMemento__authenticationSource__gqlv_property {
 type rich__causeway_applib_UserMemento__avatarUrl__gqlv_property {
   datatype: String
   disabled: String
-  get: String
+  get: Url
   hidden: Boolean
-  set(avatarUrl: String): rich__causeway_applib_UserMemento
-  validate(avatarUrl: String): String
+  set(avatarUrl: Url): rich__causeway_applib_UserMemento
+  validate(avatarUrl: Url): String
 }
 
 type rich__causeway_applib_UserMemento__gqlv_meta {
@@ -413,10 +443,10 @@ type rich__causeway_applib_UserMemento__isImpersonating__gqlv_property {
 type rich__causeway_applib_UserMemento__languageLocale__gqlv_property {
   datatype: String
   disabled: String
-  get: String
+  get: Locale
   hidden: Boolean
-  set(languageLocale: String): rich__causeway_applib_UserMemento
-  validate(languageLocale: String): String
+  set(languageLocale: Locale): rich__causeway_applib_UserMemento
+  validate(languageLocale: Locale): String
 }
 
 "tenancy (or tenancies) this user has access to"
@@ -442,10 +472,10 @@ type rich__causeway_applib_UserMemento__name__gqlv_property {
 type rich__causeway_applib_UserMemento__numberFormatLocale__gqlv_property {
   datatype: String
   disabled: String
-  get: String
+  get: Locale
   hidden: Boolean
-  set(numberFormatLocale: String): rich__causeway_applib_UserMemento
-  validate(numberFormatLocale: String): String
+  set(numberFormatLocale: Locale): rich__causeway_applib_UserMemento
+  validate(numberFormatLocale: Locale): String
 }
 
 type rich__causeway_applib_UserMemento__realName__gqlv_property {
@@ -468,10 +498,10 @@ type rich__causeway_applib_UserMemento__roles__gqlv_collection {
 type rich__causeway_applib_UserMemento__timeFormatLocale__gqlv_property {
   datatype: String
   disabled: String
-  get: String
+  get: Locale
   hidden: Boolean
-  set(timeFormatLocale: String): rich__causeway_applib_UserMemento
-  validate(timeFormatLocale: String): String
+  set(timeFormatLocale: Locale): rich__causeway_applib_UserMemento
+  validate(timeFormatLocale: Locale): String
 }
 
 type rich__causeway_applib_UserMenu {
@@ -1149,10 +1179,10 @@ type rich__causeway_metamodel_MetamodelInspectView {
 type rich__causeway_metamodel_MetamodelInspectView__details__gqlv_property {
   datatype: String
   disabled: String
-  get: String!
+  get: Markup!
   hidden: Boolean
-  set(details: String!): rich__causeway_metamodel_MetamodelInspectView
-  validate(details: String): String
+  set(details: UnsupportedValue!): rich__causeway_metamodel_MetamodelInspectView
+  validate(details: UnsupportedValue): String
 }
 
 type rich__causeway_metamodel_MetamodelInspectView__gqlv_meta {
@@ -1202,10 +1232,10 @@ type rich__causeway_metamodel_MetamodelInspectView__inspect__gqlv_action_params
 type rich__causeway_metamodel_MetamodelInspectView__tree__gqlv_property {
   datatype: String
   disabled: String
-  get: String!
+  get: UnsupportedValue!
   hidden: Boolean
-  set(tree: String!): rich__causeway_metamodel_MetamodelInspectView
-  validate(tree: String): String
+  set(tree: UnsupportedValue!): rich__causeway_metamodel_MetamodelInspectView
+  validate(tree: UnsupportedValue): String
 }
 
 type rich__causeway_security_LoginRedirect {
@@ -1296,8 +1326,8 @@ type rich__causeway_testing_fixtures_FixtureResult__object__gqlv_property {
   datatype: String
   disabled: String
   hidden: Boolean
-  set(object: String!): rich__causeway_testing_fixtures_FixtureResult
-  validate(object: String): String
+  set(object: UnsupportedValue!): rich__causeway_testing_fixtures_FixtureResult
+  validate(object: UnsupportedValue): String
 }
 
 type rich__java_lang_Record {
@@ -1401,10 +1431,10 @@ type rich__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__gqlv_meta
 type rich__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__tree__gqlv_property {
   datatype: String
   disabled: String
-  get: String!
+  get: UnsupportedValue!
   hidden: Boolean
-  set(tree: String!): rich__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__gqlv_union
-  validate(tree: String): String
+  set(tree: UnsupportedValue!): rich__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__gqlv_union
+  validate(tree: UnsupportedValue): String
 }
 
 type rich__org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript {
@@ -1607,6 +1637,40 @@ type rich__university_calc_Calculator {
   createEditableMementoViewModel: rich__university_calc_Calculator__createEditableMementoViewModel__gqlv_action
   "Create Shared View Model"
   createSharedViewModel: rich__university_calc_Calculator__createSharedViewModel__gqlv_action
+  "Echo Application Feature Id"
+  echoApplicationFeatureId: rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action
+  "Echo Blob"
+  echoBlob: rich__university_calc_Calculator__echoBlob__gqlv_action
+  "Echo Bookmark"
+  echoBookmark: rich__university_calc_Calculator__echoBookmark__gqlv_action
+  "Echo Clob"
+  echoClob: rich__university_calc_Calculator__echoClob__gqlv_action
+  "Echo Complex Number"
+  echoComplexNumber: rich__university_calc_Calculator__echoComplexNumber__gqlv_action
+  "Echo Identifier"
+  echoIdentifier: rich__university_calc_Calculator__echoIdentifier__gqlv_action
+  "Echo Java Sql Date"
+  echoJavaSqlDate: rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action
+  "Echo Java Sql Time"
+  echoJavaSqlTime: rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action
+  "Echo Java Sql Timestamp"
+  echoJavaSqlTimestamp: rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action
+  "Echo Java Util Date"
+  echoJavaUtilDate: rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action
+  "Echo Local Date Time"
+  echoLocalDateTime: rich__university_calc_Calculator__echoLocalDateTime__gqlv_action
+  "Echo Local Resource Path"
+  echoLocalResourcePath: rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action
+  "Echo Locale"
+  echoLocale: rich__university_calc_Calculator__echoLocale__gqlv_action
+  "Echo Markup"
+  echoMarkup: rich__university_calc_Calculator__echoMarkup__gqlv_action
+  "Echo Password"
+  echoPassword: rich__university_calc_Calculator__echoPassword__gqlv_action
+  "Echo Unmapped Value"
+  echoUnmappedValue: rich__university_calc_Calculator__echoUnmappedValue__gqlv_action
+  "Echo Url"
+  echoUrl: rich__university_calc_Calculator__echoUrl__gqlv_action
   "Jdk 8 Local Plus Days"
   jdk8LocalPlusDays: rich__university_calc_Calculator__jdk8LocalPlusDays__gqlv_action
   "Jdk 8 Local Plus Hours And Minutes"
@@ -1621,10 +1685,26 @@ type rich__university_calc_Calculator {
   nextMonth: rich__university_calc_Calculator__nextMonth__gqlv_action
   "Not"
   not: rich__university_calc_Calculator__not__gqlv_action
+  "Optional Blob"
+  optionalBlob: rich__university_calc_Calculator__optionalBlob__gqlv_action
+  "Optional Local Date Time"
+  optionalLocalDateTime: rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action
   "Or"
   or: rich__university_calc_Calculator__or__gqlv_action
   "Read Shared View Model"
   readSharedViewModel: rich__university_calc_Calculator__readSharedViewModel__gqlv_action
+  "Sample Blob"
+  sampleBlob: rich__university_calc_Calculator__sampleBlob__gqlv_action
+  "Sample Clob"
+  sampleClob: rich__university_calc_Calculator__sampleClob__gqlv_action
+  "Sample Identifier"
+  sampleIdentifier: rich__university_calc_Calculator__sampleIdentifier__gqlv_action
+  "Sample Markup"
+  sampleMarkup: rich__university_calc_Calculator__sampleMarkup__gqlv_action
+  "Sample Unmapped Value"
+  sampleUnmappedValue: rich__university_calc_Calculator__sampleUnmappedValue__gqlv_action
+  "Secret Password"
+  secretPassword: rich__university_calc_Calculator__secretPassword__gqlv_action
   "Some Locale"
   someLocale: rich__university_calc_Calculator__someLocale__gqlv_action
   "Some Url"
@@ -2245,6 +2325,556 @@ type rich__university_calc_Calculator__createSharedViewModel__gqlv_action_invoke
   target: String
 }
 
+type rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: ApplicationFeatureId!): rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_params
+  validate(value: ApplicationFeatureId): String
+}
+
+type rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_args {
+  value: ApplicationFeatureId
+}
+
+type rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_args
+  results: ApplicationFeatureId
+  target: String
+}
+
+type rich__university_calc_Calculator__echoApplicationFeatureId__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoApplicationFeatureId__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoApplicationFeatureId__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: ApplicationFeatureId): String
+  hidden: Boolean
+  validity(value: ApplicationFeatureId): String
+}
+
+type rich__university_calc_Calculator__echoBlob__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: BlobInput!): rich__university_calc_Calculator__echoBlob__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoBlob__gqlv_action_params
+  validate(value: BlobInput): String
+}
+
+type rich__university_calc_Calculator__echoBlob__gqlv_action_args {
+  value: BlobValue
+}
+
+type rich__university_calc_Calculator__echoBlob__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoBlob__gqlv_action_args
+  results: BlobValue
+  target: String
+}
+
+type rich__university_calc_Calculator__echoBlob__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoBlob__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoBlob__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: BlobInput): String
+  fileAccept: String
+  hidden: Boolean
+  inlineInputMaxBytes: Int
+  resourceInputMode: String
+  validity(value: BlobInput): String
+}
+
+type rich__university_calc_Calculator__echoBookmark__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: Bookmark!): rich__university_calc_Calculator__echoBookmark__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoBookmark__gqlv_action_params
+  validate(value: Bookmark): String
+}
+
+type rich__university_calc_Calculator__echoBookmark__gqlv_action_args {
+  value: Bookmark
+}
+
+type rich__university_calc_Calculator__echoBookmark__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoBookmark__gqlv_action_args
+  results: Bookmark
+  target: String
+}
+
+type rich__university_calc_Calculator__echoBookmark__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoBookmark__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoBookmark__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: Bookmark): String
+  hidden: Boolean
+  validity(value: Bookmark): String
+}
+
+type rich__university_calc_Calculator__echoClob__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: ClobInput!): rich__university_calc_Calculator__echoClob__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoClob__gqlv_action_params
+  validate(value: ClobInput): String
+}
+
+type rich__university_calc_Calculator__echoClob__gqlv_action_args {
+  value: ClobValue
+}
+
+type rich__university_calc_Calculator__echoClob__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoClob__gqlv_action_args
+  results: ClobValue
+  target: String
+}
+
+type rich__university_calc_Calculator__echoClob__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoClob__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoClob__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: ClobInput): String
+  fileAccept: String
+  hidden: Boolean
+  inlineInputMaxBytes: Int
+  resourceInputMode: String
+  validity(value: ClobInput): String
+}
+
+type rich__university_calc_Calculator__echoComplexNumber__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: ComplexNumber!): rich__university_calc_Calculator__echoComplexNumber__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoComplexNumber__gqlv_action_params
+  validate(value: ComplexNumber): String
+}
+
+type rich__university_calc_Calculator__echoComplexNumber__gqlv_action_args {
+  value: ComplexNumber
+}
+
+type rich__university_calc_Calculator__echoComplexNumber__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoComplexNumber__gqlv_action_args
+  results: ComplexNumber
+  target: String
+}
+
+type rich__university_calc_Calculator__echoComplexNumber__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoComplexNumber__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoComplexNumber__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: ComplexNumber): String
+  hidden: Boolean
+  validity(value: ComplexNumber): String
+}
+
+type rich__university_calc_Calculator__echoIdentifier__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: UnsupportedValue!): rich__university_calc_Calculator__echoIdentifier__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoIdentifier__gqlv_action_params
+  validate(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoIdentifier__gqlv_action_args {
+  value: UnsupportedValue
+}
+
+type rich__university_calc_Calculator__echoIdentifier__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoIdentifier__gqlv_action_args
+  results: UnsupportedValue
+  target: String
+}
+
+type rich__university_calc_Calculator__echoIdentifier__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoIdentifier__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoIdentifier__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: UnsupportedValue): String
+  hidden: Boolean
+  validity(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: SqlDate!): rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_params
+  validate(value: SqlDate): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_args {
+  value: SqlDate
+}
+
+type rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_args
+  results: SqlDate
+  target: String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlDate__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoJavaSqlDate__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoJavaSqlDate__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: SqlDate): String
+  hidden: Boolean
+  validity(value: SqlDate): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: SqlTime!): rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_params
+  validate(value: SqlTime): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_args {
+  value: SqlTime
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_args
+  results: SqlTime
+  target: String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTime__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoJavaSqlTime__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTime__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: SqlTime): String
+  hidden: Boolean
+  validity(value: SqlTime): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: SqlTimestamp!): rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_params
+  validate(value: SqlTimestamp): String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_args {
+  value: SqlTimestamp
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_args
+  results: SqlTimestamp
+  target: String
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTimestamp__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoJavaSqlTimestamp__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoJavaSqlTimestamp__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: SqlTimestamp): String
+  hidden: Boolean
+  validity(value: SqlTimestamp): String
+}
+
+type rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: LegacyDateTime!): rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_params
+  validate(value: LegacyDateTime): String
+}
+
+type rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_args {
+  value: LegacyDateTime
+}
+
+type rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_args
+  results: LegacyDateTime
+  target: String
+}
+
+type rich__university_calc_Calculator__echoJavaUtilDate__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoJavaUtilDate__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoJavaUtilDate__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: LegacyDateTime): String
+  hidden: Boolean
+  validity(value: LegacyDateTime): String
+}
+
+type rich__university_calc_Calculator__echoLocalDateTime__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: LocalDateTime!): rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_params
+  validate(value: LocalDateTime): String
+}
+
+type rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_args {
+  value: LocalDateTime
+}
+
+type rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_args
+  results: LocalDateTime
+  target: String
+}
+
+type rich__university_calc_Calculator__echoLocalDateTime__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoLocalDateTime__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoLocalDateTime__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: LocalDateTime): String
+  hidden: Boolean
+  validity(value: LocalDateTime): String
+}
+
+type rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: LocalResourcePathInput!): rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_params
+  validate(value: LocalResourcePathInput): String
+}
+
+type rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_args {
+  value: LocalResourcePathValue
+}
+
+type rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_args
+  results: LocalResourcePathValue
+  target: String
+}
+
+type rich__university_calc_Calculator__echoLocalResourcePath__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoLocalResourcePath__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoLocalResourcePath__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: LocalResourcePathInput): String
+  hidden: Boolean
+  validity(value: LocalResourcePathInput): String
+}
+
+type rich__university_calc_Calculator__echoLocale__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: Locale!): rich__university_calc_Calculator__echoLocale__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoLocale__gqlv_action_params
+  validate(value: Locale): String
+}
+
+type rich__university_calc_Calculator__echoLocale__gqlv_action_args {
+  value: Locale
+}
+
+type rich__university_calc_Calculator__echoLocale__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoLocale__gqlv_action_args
+  results: Locale
+  target: String
+}
+
+type rich__university_calc_Calculator__echoLocale__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoLocale__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoLocale__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: Locale): String
+  hidden: Boolean
+  validity(value: Locale): String
+}
+
+type rich__university_calc_Calculator__echoMarkup__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: UnsupportedValue!): rich__university_calc_Calculator__echoMarkup__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoMarkup__gqlv_action_params
+  validate(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoMarkup__gqlv_action_args {
+  value: Markup
+}
+
+type rich__university_calc_Calculator__echoMarkup__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoMarkup__gqlv_action_args
+  results: Markup
+  target: String
+}
+
+type rich__university_calc_Calculator__echoMarkup__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoMarkup__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoMarkup__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: UnsupportedValue): String
+  hidden: Boolean
+  validity(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoPassword__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: Password!): rich__university_calc_Calculator__echoPassword__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoPassword__gqlv_action_params
+  validate(value: Password): String
+}
+
+type rich__university_calc_Calculator__echoPassword__gqlv_action_args {
+  value: Password
+}
+
+type rich__university_calc_Calculator__echoPassword__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoPassword__gqlv_action_args
+  results: Password
+  target: String
+}
+
+type rich__university_calc_Calculator__echoPassword__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoPassword__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoPassword__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: Password): String
+  hidden: Boolean
+  validity(value: Password): String
+}
+
+type rich__university_calc_Calculator__echoUnmappedValue__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: UnsupportedValue!): rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_params
+  validate(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_args {
+  value: UnsupportedValue
+}
+
+type rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_args
+  results: UnsupportedValue
+  target: String
+}
+
+type rich__university_calc_Calculator__echoUnmappedValue__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoUnmappedValue__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoUnmappedValue__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: UnsupportedValue): String
+  hidden: Boolean
+  validity(value: UnsupportedValue): String
+}
+
+type rich__university_calc_Calculator__echoUrl__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: Url!): rich__university_calc_Calculator__echoUrl__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__echoUrl__gqlv_action_params
+  validate(value: Url): String
+}
+
+type rich__university_calc_Calculator__echoUrl__gqlv_action_args {
+  value: Url
+}
+
+type rich__university_calc_Calculator__echoUrl__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__echoUrl__gqlv_action_args
+  results: Url
+  target: String
+}
+
+type rich__university_calc_Calculator__echoUrl__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__echoUrl__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__echoUrl__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: Url): String
+  hidden: Boolean
+  validity(value: Url): String
+}
+
 type rich__university_calc_Calculator__jdk8LocalPlusDays__date__gqlv_action_parameter {
   datatype: String
   disabled(date: String): String
@@ -2580,6 +3210,73 @@ type rich__university_calc_Calculator__not__x__gqlv_action_parameter {
   validity(x: Boolean): String
 }
 
+type rich__university_calc_Calculator__optionalBlob__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: BlobInput): rich__university_calc_Calculator__optionalBlob__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__optionalBlob__gqlv_action_params
+  validate(value: BlobInput): String
+}
+
+type rich__university_calc_Calculator__optionalBlob__gqlv_action_args {
+  value: BlobValue
+}
+
+type rich__university_calc_Calculator__optionalBlob__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__optionalBlob__gqlv_action_args
+  results: BlobValue
+  target: String
+}
+
+type rich__university_calc_Calculator__optionalBlob__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__optionalBlob__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__optionalBlob__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: BlobInput): String
+  fileAccept: String
+  hidden: Boolean
+  inlineInputMaxBytes: Int
+  resourceInputMode: String
+  validity(value: BlobInput): String
+}
+
+type rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(value: LocalDateTime): rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_invoke
+  "Parameters of this action"
+  params: rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_params
+  validate(value: LocalDateTime): String
+}
+
+type rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_args {
+  value: LocalDateTime
+}
+
+type rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_args
+  results: LocalDateTime
+  target: String
+}
+
+type rich__university_calc_Calculator__optionalLocalDateTime__gqlv_action_params {
+  "Value"
+  value: rich__university_calc_Calculator__optionalLocalDateTime__value__gqlv_action_parameter
+}
+
+type rich__university_calc_Calculator__optionalLocalDateTime__value__gqlv_action_parameter {
+  datatype: String
+  disabled(value: LocalDateTime): String
+  hidden: Boolean
+  validity(value: LocalDateTime): String
+}
+
 type rich__university_calc_Calculator__or__gqlv_action {
   disabled: String
   hidden: Boolean
@@ -2655,6 +3352,78 @@ type rich__university_calc_Calculator__readSharedViewModel__viewModel__gqlv_acti
   validity(viewModel: rich__university_calc_EditableMementoViewModel__gqlv_input): String
 }
 
+type rich__university_calc_Calculator__sampleBlob__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__sampleBlob__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__sampleBlob__gqlv_action_invoke {
+  results: BlobValue
+  target: String
+}
+
+type rich__university_calc_Calculator__sampleClob__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__sampleClob__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__sampleClob__gqlv_action_invoke {
+  results: ClobValue
+  target: String
+}
+
+type rich__university_calc_Calculator__sampleIdentifier__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__sampleIdentifier__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__sampleIdentifier__gqlv_action_invoke {
+  results: UnsupportedValue
+  target: String
+}
+
+type rich__university_calc_Calculator__sampleMarkup__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__sampleMarkup__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__sampleMarkup__gqlv_action_invoke {
+  results: Markup
+  target: String
+}
+
+type rich__university_calc_Calculator__sampleUnmappedValue__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__sampleUnmappedValue__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__sampleUnmappedValue__gqlv_action_invoke {
+  results: UnsupportedValue
+  target: String
+}
+
+type rich__university_calc_Calculator__secretPassword__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke: rich__university_calc_Calculator__secretPassword__gqlv_action_invoke
+  validate: String
+}
+
+type rich__university_calc_Calculator__secretPassword__gqlv_action_invoke {
+  results: Password
+  target: String
+}
+
 type rich__university_calc_Calculator__someLocale__gqlv_action {
   disabled: String
   hidden: Boolean
@@ -2663,7 +3432,7 @@ type rich__university_calc_Calculator__someLocale__gqlv_action {
 }
 
 type rich__university_calc_Calculator__someLocale__gqlv_action_invoke {
-  results: String
+  results: Locale
   target: String
 }
 
@@ -2675,7 +3444,7 @@ type rich__university_calc_Calculator__someUrl__gqlv_action {
 }
 
 type rich__university_calc_Calculator__someUrl__gqlv_action_invoke {
-  results: String
+  results: Url
   target: String
 }
 
@@ -3845,7 +4614,7 @@ type rich__university_calc_calculatorHyphenated__someLocale__gqlv_action {
 }
 
 type rich__university_calc_calculatorHyphenated__someLocale__gqlv_action_invoke {
-  results: String
+  results: Locale
   target: String
 }
 
@@ -3857,7 +4626,7 @@ type rich__university_calc_calculatorHyphenated__someUrl__gqlv_action {
 }
 
 type rich__university_calc_calculatorHyphenated__someUrl__gqlv_action_invoke {
-  results: String
+  results: Url
   target: String
 }
 
@@ -4579,14 +5348,18 @@ type rich__university_dept_StaffMember__hiddenPhoto__gqlv_property {
   disabled: String
   get: rich__university_dept_StaffMember__hiddenPhoto__gqlv_property_lob
   hidden: Boolean
-  set(hiddenPhoto: String!): rich__university_dept_StaffMember
-  validate(hiddenPhoto: String): String
+  set(hiddenPhoto: BlobInput!): rich__university_dept_StaffMember
+  validate(hiddenPhoto: BlobInput): String
 }
 
 type rich__university_dept_StaffMember__hiddenPhoto__gqlv_property_lob {
+  byteLength: Int
   bytes: String
+  fileAccept: String
+  inlineInputMaxBytes: Int
   mimeType: String
   name: String
+  transferMode: String
 }
 
 type rich__university_dept_StaffMember__name__gqlv_property {
@@ -4603,14 +5376,18 @@ type rich__university_dept_StaffMember__photo__gqlv_property {
   disabled: String
   get: rich__university_dept_StaffMember__photo__gqlv_property_lob
   hidden: Boolean
-  set(photo: String): rich__university_dept_StaffMember
-  validate(photo: String): String
+  set(photo: BlobInput): rich__university_dept_StaffMember
+  validate(photo: BlobInput): String
 }
 
 type rich__university_dept_StaffMember__photo__gqlv_property_lob {
+  byteLength: Int
   bytes: String
+  fileAccept: String
+  inlineInputMaxBytes: Int
   mimeType: String
   name: String
+  transferMode: String
 }
 
 type rich__university_dept_StaffMember__profile__gqlv_property {
@@ -4618,14 +5395,19 @@ type rich__university_dept_StaffMember__profile__gqlv_property {
   disabled: String
   get: rich__university_dept_StaffMember__profile__gqlv_property_lob
   hidden: Boolean
-  set(profile: String!): rich__university_dept_StaffMember
-  validate(profile: String): String
+  set(profile: ClobInput!): rich__university_dept_StaffMember
+  validate(profile: ClobInput): String
 }
 
 type rich__university_dept_StaffMember__profile__gqlv_property_lob {
+  byteLength: Int
+  characterLength: Int
   chars: String
+  fileAccept: String
+  inlineInputMaxBytes: Int
   mimeType: String
   name: String
+  transferMode: String
 }
 
 type rich__university_dept_Staff__createStaffMember__department__gqlv_action_parameter {
@@ -4761,16 +5543,16 @@ type simple__causeway_applib_UserMemento {
   _meta: simple__causeway_applib_UserMemento__gqlv_meta
   authenticationCode: String!
   authenticationSource: rich__org_apache_causeway_applib_services_user_UserMemento_AuthenticationSource__gqlv_enum!
-  avatarUrl: String
+  avatarUrl: Url
   isImpersonating: Boolean!
-  languageLocale: String
+  languageLocale: Locale
   multiTenancyToken: String
   name: String!
-  numberFormatLocale: String
+  numberFormatLocale: Locale
   realName: String
   "roles associated with this user"
   roles: [simple__causeway_applib_RoleMemento]
-  timeFormatLocale: String
+  timeFormatLocale: Locale
 }
 
 type simple__causeway_applib_UserMemento__gqlv_meta {
@@ -4993,10 +5775,10 @@ type simple__causeway_feat_ApplicationType__gqlv_meta {
 type simple__causeway_metamodel_MetamodelInspectView {
   "Object metadata"
   _meta: simple__causeway_metamodel_MetamodelInspectView__gqlv_meta
-  details: String!
+  details: Markup!
   "Opens a meta-model inspection view for given fully qualified domain class"
   inspect(fullyQualifiedClassName: String!): simple__causeway_metamodel_MetamodelInspectView
-  tree: String!
+  tree: UnsupportedValue!
 }
 
 type simple__causeway_metamodel_MetamodelInspectView__gqlv_meta {
@@ -5133,7 +5915,7 @@ type simple__java_util_stream_Stream__gqlv_meta {
 type simple__org_apache_causeway_applib_graph_tree_MasterDetailTreeView {
   "Object metadata"
   _meta: simple__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__gqlv_meta
-  tree: String!
+  tree: UnsupportedValue!
 }
 
 type simple__org_apache_causeway_applib_graph_tree_MasterDetailTreeView__gqlv_meta {
@@ -5209,6 +5991,40 @@ type simple__university_calc_Calculator {
   createEditableMementoViewModel: simple__university_calc_EditableMementoViewModel
   "Create Shared View Model"
   createSharedViewModel: simple__university_calc_EditableMementoViewModel
+  "Echo Application Feature Id"
+  echoApplicationFeatureId(value: ApplicationFeatureId!): ApplicationFeatureId
+  "Echo Blob"
+  echoBlob(value: BlobInput!): BlobValue
+  "Echo Bookmark"
+  echoBookmark(value: Bookmark!): Bookmark
+  "Echo Clob"
+  echoClob(value: ClobInput!): ClobValue
+  "Echo Complex Number"
+  echoComplexNumber(value: ComplexNumber!): ComplexNumber
+  "Echo Identifier"
+  echoIdentifier(value: UnsupportedValue!): UnsupportedValue
+  "Echo Java Sql Date"
+  echoJavaSqlDate(value: SqlDate!): SqlDate
+  "Echo Java Sql Time"
+  echoJavaSqlTime(value: SqlTime!): SqlTime
+  "Echo Java Sql Timestamp"
+  echoJavaSqlTimestamp(value: SqlTimestamp!): SqlTimestamp
+  "Echo Java Util Date"
+  echoJavaUtilDate(value: LegacyDateTime!): LegacyDateTime
+  "Echo Local Date Time"
+  echoLocalDateTime(value: LocalDateTime!): LocalDateTime
+  "Echo Local Resource Path"
+  echoLocalResourcePath(value: LocalResourcePathInput!): LocalResourcePathValue
+  "Echo Locale"
+  echoLocale(value: Locale!): Locale
+  "Echo Markup"
+  echoMarkup(value: UnsupportedValue!): Markup
+  "Echo Password"
+  echoPassword(value: Password!): Password
+  "Echo Unmapped Value"
+  echoUnmappedValue(value: UnsupportedValue!): UnsupportedValue
+  "Echo Url"
+  echoUrl(value: Url!): Url
   "Jdk 8 Local Plus Days"
   jdk8LocalPlusDays(date: String!, numDays: Int!): String
   "Jdk 8 Local Plus Hours And Minutes"
@@ -5223,14 +6039,30 @@ type simple__university_calc_Calculator {
   nextMonth(month: rich__org_apache_causeway_viewer_graphql_viewer_test_domain_calc_Month__gqlv_enum!): rich__org_apache_causeway_viewer_graphql_viewer_test_domain_calc_Month__gqlv_enum
   "Not"
   not(x: Boolean!): Boolean
+  "Optional Blob"
+  optionalBlob(value: BlobInput): BlobValue
+  "Optional Local Date Time"
+  optionalLocalDateTime(value: LocalDateTime): LocalDateTime
   "Or"
   or(x: Boolean!, y: Boolean!): Boolean
   "Read Shared View Model"
   readSharedViewModel(viewModel: simple__university_calc_EditableMementoViewModel__gqlv_input!): Int
+  "Sample Blob"
+  sampleBlob: BlobValue
+  "Sample Clob"
+  sampleClob: ClobValue
+  "Sample Identifier"
+  sampleIdentifier: UnsupportedValue
+  "Sample Markup"
+  sampleMarkup: Markup
+  "Sample Unmapped Value"
+  sampleUnmappedValue: UnsupportedValue
+  "Secret Password"
+  secretPassword: Password
   "Some Locale"
-  someLocale: String
+  someLocale: Locale
   "Some Url"
-  someUrl: String
+  someUrl: Url
   "Some Uuid"
   someUuid: UUID
 }
@@ -5305,9 +6137,9 @@ type simple__university_calc_calculatorHyphenated {
   "Or"
   or(x: Boolean!, y: Boolean!): Boolean
   "Some Locale"
-  someLocale: String
+  someLocale: Locale
   "Some Url"
-  someUrl: String
+  someUrl: Url
   "Some Uuid"
   someUuid: UUID
 }
@@ -5466,6 +6298,12 @@ type simple__university_dept_StaffMember__profile__gqlv_member {
   name: String
 }
 
+"Browser opening strategy for a Causeway local resource path."
+enum OpenUrlStrategy {
+  NEW_WINDOW
+  SAME_WINDOW
+}
+
 enum logicalTypeNames__gqlv_enum {
   "causeway.applib.DomainObjectList"
   causeway_applib_DomainObjectList
@@ -5547,21 +6385,86 @@ enum rich__org_apache_causeway_viewer_graphql_viewer_test_domain_dept_Grade__gql
   SENIOR_LECTURER
 }
 
+"A URL-safe encoded Causeway application feature identifier."
+scalar ApplicationFeatureId
+
+"A Causeway bookmark in logical-type:identifier form."
+scalar Bookmark
+
 "An 8-bit signed integer"
 scalar Byte
 
+"Application-defined reversible complex number in algebraic form, such as 3+4i or 3-4i."
+scalar ComplexNumber
+
 "A slightly refined version of RFC-3339 compliant DateTime Scalar"
 scalar DateTime
 
+"An ISO-8601 UTC instant used for java.util.Date."
+scalar LegacyDateTime
+
+"An ISO-8601 local date-time without an offset or zone; fractional seconds are preserved."
+scalar LocalDateTime
+
+"An IETF BCP 47 language tag used for java.util.Locale."
+scalar Locale
+
+"Causeway markup HTML. This scalar is output-only and clients must treat the content as markup."
+scalar Markup
+
+"A protected Causeway password value. Input constructs the value; output is always suppressed."
+scalar Password
+
 "A 16-bit signed integer"
 scalar Short
 
+"An ISO-8601 date-only value used for java.sql.Date."
+scalar SqlDate
+
+"An ISO-8601 time-only value at second precision used for java.sql.Time."
+scalar SqlTime
+
+"An ISO-8601 local date-time used for java.sql.Timestamp; fractional seconds are preserved."
+scalar SqlTimestamp
+
 "An RFC-3339 compliant Full Time Scalar"
 scalar Time
 
 "A universally unique identifier compliant UUID Scalar"
 scalar UUID
 
+"Unsupported Causeway value. Strict output is redacted and input requires an explicit reversible ScalarMarshaller or TypeMapper."
+scalar UnsupportedValue
+
+"An absolute URL represented by its normalized external form."
+scalar Url
+
+"Bounded inline Causeway Blob input."
+input BlobInput {
+  "RFC 4648 base64 content, bounded after decoding."
+  base64: String!
+  "IANA media type accepted by the target member."
+  mimeType: String!
+  "Resource filename."
+  name: String!
+}
+
+"Bounded inline Causeway Clob input."
+input ClobInput {
+  "UTF-8 character content, bounded by encoded byte length."
+  chars: String!
+  "IANA media type accepted by the target member."
+  mimeType: String!
+  "Resource filename."
+  name: String!
+}
+
+"A Causeway local resource path and browser opening strategy."
+input LocalResourcePathInput {
+  openUrlStrategy: OpenUrlStrategy!
+  path: String!
+}
+
 input rich__causeway_applib_DomainObjectList__gqlv_input {
   "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
diff --git a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
index 7ff2e97cc7b..05c1e03808f 100644
--- a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
+++ b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
@@ -4,7 +4,7 @@
       "university_calc_Calculator" : {
         "someLocale" : {
           "invoke" : {
-            "results" : "en_GB"
+            "results" : "en-GB"
           }
         }
       }


(causeway) 01/04: promotes rich GraphQL value and resource semantics

Posted by da...@apache.org.
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 b6ec76fc08d56dd09e1dfca6129244e7a5a96e4f
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Aug 18 09:36:11 2026 +0100

    promotes rich GraphQL value and resource semantics
---
 .../.openspec.yaml                                 |  0
 .../design.md                                      | 35 ++++++++++++++--------
 .../proposal.md                                    |  3 +-
 .../specs/rich-graphql-value-semantics/spec.md     |  0
 .../tasks.md                                       |  0
 .../planned-changes/web-component-viewer/README.md |  7 ++---
 6 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/.openspec.yaml b/openspec/changes/add-rich-graphql-value-and-resource-semantics/.openspec.yaml
similarity index 100%
rename from openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/.openspec.yaml
rename to openspec/changes/add-rich-graphql-value-and-resource-semantics/.openspec.yaml
diff --git a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/design.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
similarity index 58%
rename from openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/design.md
rename to openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
index e729f2b90dc..ffb4461ce46 100644
--- a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/design.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
@@ -6,7 +6,8 @@ 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`.
 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 resource-link safety proposal separately corrects malformed URLs, forbidden link exposure, and metadata-versus-content 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.
 
 ## Goals / Non-Goals
 
@@ -50,22 +51,29 @@ The change does not add a central datatype catalogue that duplicates GraphQL's t
 ### Use canonical standard formats
 
 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.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
 
-Applications register a reversible marshaller and its GraphQL scalar or structured input and output mapping.
-Composite values may use dedicated generated objects or an application-defined scalar when both directions are deterministic.
-Opaque values remain unsupported for input rather than falling back to `toString()` or arbitrary constructor discovery.
+Applications register a reversible `ScalarMarshaller` and application-defined GraphQL scalar for scalar-shaped custom values.
+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.
 
 ### Layer resource transfer above safe references
 
-Resource metadata can expose filename, media type, bounded size information, transfer mode, and member acceptance constraints without retrieving content.
-A registered input strategy defines how authorized bounded content reconstructs Blob, Clob, or another declared resource type.
+Resource metadata exposes filename, media type, bounded size information, transfer mode, and member `fileAccept` constraints without retrieving content.
+Blob input uses a generated `BlobInput` object containing `name`, `mimeType`, and base64 content.
+Clob input uses a generated `ClobInput` object containing `name`, `mimeType`, and character content.
+Both reconstruct Causeway values only when value-content policy is enabled and the decoded content satisfies the configured inline-input byte limit and member acceptance constraint.
 
-Large content uses the corrected secured resource-reference contract.
-Inline input or output is permitted only under configured media and size limits.
+Action results use generated metadata objects with bounded inline content only when value-content policy is enabled and content is within the configured inline-output byte limit.
+Larger property content continues to use the corrected secured resource-reference contract.
+Inline limits default to one mebibyte and are independently configurable for input and output.
 Password and hidden values never enter the generic resource path.
 
 ## Risks / Trade-offs
@@ -78,11 +86,12 @@ Password and hidden values never enter the generic resource path.
 ## Migration Plan
 
 Add standard marshallers ahead of generic fallback.
-Introduce diagnostics for non-reversible input types before strict input rejection becomes the default if compatibility evidence requires a transition.
+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.
 
-## Open Questions
+## Resolved Questions
 
-- Whether each temporal equivalence class should use a dedicated scalar or a documented existing scalar with distinct rich datatype identity.
-- Which resource inputs can safely use bounded inline base64 or text and at what configured limit.
-- Whether structured application values should use application-defined scalars or generated input and output objects by default.
+- 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.
diff --git a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/proposal.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
similarity index 86%
rename from openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/proposal.md
rename to openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
index b3253cca598..7d1b6140843 100644
--- a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/proposal.md
+++ b/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
@@ -5,7 +5,8 @@ The same source equivalence class includes `java.util.Date`, `java.sql.Date`, an
 Clients need reversible schema shapes and explicit extension or rejection behavior rather than input fields that accept strings and fail only during invocation.
 
 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 handled first by `fix-rich-graphql-resource-link-safety` under entries `REF-RESOURCE-02` and `REF-RESOURCE-03`.
+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`.
+This change builds on its same-origin `ResourcePath`, independent value-content policy, forbidden-field omission, and dereference authorization rather than replacing them.
 
 ## What Changes
 
diff --git a/openspec/planned-changes/web-component-viewer/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
similarity index 100%
rename from openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
rename to openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
diff --git a/openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/tasks.md b/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
similarity index 100%
rename from openspec/planned-changes/web-component-viewer/add-rich-graphql-value-and-resource-semantics/tasks.md
rename to openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
diff --git a/openspec/planned-changes/web-component-viewer/README.md b/openspec/planned-changes/web-component-viewer/README.md
index ce0f645f6f2..a0cfbe3c9a5 100644
--- a/openspec/planned-changes/web-component-viewer/README.md
+++ b/openspec/planned-changes/web-component-viewer/README.md
@@ -2,9 +2,8 @@
 
 These are fully drafted follow-on changes for the Causeway web-component viewer programme.
 They are held outside `openspec/changes/` because the repository permits only one active OpenSpec change at a time.
-The foundation change `establish-graphql-web-component-foundation`, read-only change `add-read-only-domain-web-components`, interaction change `add-domain-web-component-interactions`, analysis change `analyze-rich-graphql-referenceapp-coverage`, and first P0 change `fix-rich-graphql-object-interaction-correctness` are archived.
-The second P0 change, `fix-rich-graphql-resource-link-safety`, is now active.
-The remaining drafts stay queued until that change is implemented and archived.
+The foundation change `establish-graphql-web-component-foundation`, read-only change `add-read-only-domain-web-components`, interaction change `add-domain-web-component-interactions`, analysis change `analyze-rich-graphql-referenceapp-coverage`, and both P0 correctness changes are archived.
+The remaining drafts are queued for serial promotion, beginning with `add-rich-graphql-value-and-resource-semantics`.
 
 Each child directory is a complete planned change containing `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, and delta specifications.
 After the active change is archived, promote the next draft verbatim:
@@ -26,7 +25,7 @@ Matrix entry references point to `viewers/graphql/adoc/modules/ROOT/examples/ref
 | 2 | `add-domain-web-component-interactions` *(archived)* | Complete | Archived read-only components | MODIFIED `domain-web-components` |
 | 3 | `analyze-rich-graphql-referenceapp-coverage` *(archived; analysis only)* | Complete | Archived foundation and components; pinned reference application | NEW `rich-graphql-referenceapp-analysis` |
 | 4 | `fix-rich-graphql-object-interaction-correctness` *(archived)* | Complete | Completed analysis | NEW `rich-graphql-object-interaction-correctness` |
-| 5 | `fix-rich-graphql-resource-link-safety` *(active)* | P0 | Completed analysis | NEW `rich-graphql-resource-link-safety` |
+| 5 | `fix-rich-graphql-resource-link-safety` *(archived)* | Complete | Completed analysis | NEW `rich-graphql-resource-link-safety` |
 | 6 | `add-rich-graphql-value-and-resource-semantics` | P1 | Object-interaction correctness and resource-link safety | NEW `rich-graphql-value-semantics` |
 | 7 | `add-rich-graphql-collection-windowing` | P1 | Object-interaction correctness | NEW `rich-graphql-collection-windowing` |
 | 8 | `add-rich-graphql-application-entry-points` | P1 | Object-interaction correctness and resource-link safety | NEW `rich-graphql-application-entry-points` |


(causeway) 04/04: archives rich GraphQL value and resource semantics

Posted by da...@apache.org.
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 182c578b644f2a989975849ae62b4329844262c9
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Aug 18 12:20:46 2026 +0100

    archives rich GraphQL value and resource semantics
---
 .../.openspec.yaml                                                | 0
 .../design.md                                                     | 0
 .../proposal.md                                                   | 0
 .../specs/rich-graphql-value-semantics/spec.md                    | 0
 .../tasks.md                                                      | 0
 openspec/planned-changes/web-component-viewer/README.md           | 6 +++---
 .../specs/rich-graphql-value-semantics/spec.md                    | 8 +++++++-
 7 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/.openspec.yaml b/openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/.openspec.yaml
similarity index 100%
rename from openspec/changes/add-rich-graphql-value-and-resource-semantics/.openspec.yaml
rename to openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/.openspec.yaml
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md b/openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/design.md
similarity index 100%
rename from openspec/changes/add-rich-graphql-value-and-resource-semantics/design.md
rename to openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/design.md
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md b/openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/proposal.md
similarity index 100%
rename from openspec/changes/add-rich-graphql-value-and-resource-semantics/proposal.md
rename to openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/proposal.md
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md b/openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
similarity index 100%
copy from openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
copy to openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md b/openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/tasks.md
similarity index 100%
rename from openspec/changes/add-rich-graphql-value-and-resource-semantics/tasks.md
rename to openspec/changes/archive/2026-08-18-add-rich-graphql-value-and-resource-semantics/tasks.md
diff --git a/openspec/planned-changes/web-component-viewer/README.md b/openspec/planned-changes/web-component-viewer/README.md
index a0cfbe3c9a5..c86584192f3 100644
--- a/openspec/planned-changes/web-component-viewer/README.md
+++ b/openspec/planned-changes/web-component-viewer/README.md
@@ -2,8 +2,8 @@
 
 These are fully drafted follow-on changes for the Causeway web-component viewer programme.
 They are held outside `openspec/changes/` because the repository permits only one active OpenSpec change at a time.
-The foundation change `establish-graphql-web-component-foundation`, read-only change `add-read-only-domain-web-components`, interaction change `add-domain-web-component-interactions`, analysis change `analyze-rich-graphql-referenceapp-coverage`, and both P0 correctness changes are archived.
-The remaining drafts are queued for serial promotion, beginning with `add-rich-graphql-value-and-resource-semantics`.
+The foundation change `establish-graphql-web-component-foundation`, read-only change `add-read-only-domain-web-components`, interaction change `add-domain-web-component-interactions`, analysis change `analyze-rich-graphql-referenceapp-coverage`, both P0 correctness changes, and value-semantics change are archived.
+The remaining drafts are queued for serial promotion, beginning with `add-rich-graphql-collection-windowing`.
 
 Each child directory is a complete planned change containing `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, and delta specifications.
 After the active change is archived, promote the next draft verbatim:
@@ -26,7 +26,7 @@ Matrix entry references point to `viewers/graphql/adoc/modules/ROOT/examples/ref
 | 3 | `analyze-rich-graphql-referenceapp-coverage` *(archived; analysis only)* | Complete | Archived foundation and components; pinned reference application | NEW `rich-graphql-referenceapp-analysis` |
 | 4 | `fix-rich-graphql-object-interaction-correctness` *(archived)* | Complete | Completed analysis | NEW `rich-graphql-object-interaction-correctness` |
 | 5 | `fix-rich-graphql-resource-link-safety` *(archived)* | Complete | Completed analysis | NEW `rich-graphql-resource-link-safety` |
-| 6 | `add-rich-graphql-value-and-resource-semantics` | P1 | Object-interaction correctness and resource-link safety | NEW `rich-graphql-value-semantics` |
+| 6 | `add-rich-graphql-value-and-resource-semantics` *(archived)* | Complete | Object-interaction correctness and resource-link safety | NEW `rich-graphql-value-semantics` |
 | 7 | `add-rich-graphql-collection-windowing` | P1 | Object-interaction correctness | NEW `rich-graphql-collection-windowing` |
 | 8 | `add-rich-graphql-application-entry-points` | P1 | Object-interaction correctness and resource-link safety | NEW `rich-graphql-application-entry-points` |
 | 9 | `add-composite-object-web-component` | P1 | Accepted value semantics, collection windows, object correctness, and structural resource safety | MODIFIED `domain-web-components` with `<causeway-object>` |
diff --git a/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md b/openspec/specs/rich-graphql-value-semantics/spec.md
similarity index 96%
rename from openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
rename to openspec/specs/rich-graphql-value-semantics/spec.md
index d56ca3a65cd..ba2be21739a 100644
--- a/openspec/changes/add-rich-graphql-value-and-resource-semantics/specs/rich-graphql-value-semantics/spec.md
+++ b/openspec/specs/rich-graphql-value-semantics/spec.md
@@ -1,4 +1,10 @@
-## ADDED Requirements
+# Rich GraphQL Value Semantics Specification
+
+## Purpose
+
+Define reversible, typed, bounded, policy-aware, and fail-closed rich GraphQL handling for standard, custom, protected, resource, and unsupported Causeway values.
+
+## Requirements
 
 ### Requirement: Reversible advertised input values
 Every rich GraphQL property or action input advertised as supported SHALL reconstruct the declared Causeway domain value from its GraphQL representation.


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

Posted by da...@apache.org.
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.