docs(spec): GraphQL access to inherited-subtype properties on Image and File fields (#34540) - #37537
fabrizzio-dotCMS wants to merge 4 commits into
Conversation
…nd File fields (#34540) Image and File fields are typed in the GraphQL schema as DotFileasset, a flat six-property object built once in a static block, instead of the real type of the asset they point at. Customer fields on DOTASSET/FILEASSET-extended content types, tags, and even the asset's own identifier are therefore unreachable, and a client cannot tell an image-style asset from a file-style one. Two decisions are recorded in the spec: - Breaking existing queries is accepted (FR-012). Five of the six properties the current view exposes are synthesized for image-style assets and have no equivalent on the real type, so preserving them would mean preserving the misdescription permanently. FR-009a additionally requires every break to be visible: `description` survives as a valid name but changes meaning, so it must be renamed rather than repurposed. - PR #35363 is superseded rather than merged (FR-013). Its benefit — sparing the client one level of nesting to reach an asset's binary properties — does not survive this feature, because the real asset type already exposes a fully described binary carrying those properties. Issue #34540 stays open afterwards, carrying the second stage, which must be designed on the shape this feature establishes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 1m 40s —— View job Spec Review — GraphQL asset-subtype fieldsI read Reviewer concerns — status
Open — not yet reflected in the spec
Minor / non-blocking
Everything else — FR-012/012a/012b expand→mark→retire framing, the ADR-0020/0022 alignment, the #35363 supersede decision (FR-013), and the FR-014 test correction — is coherent and well-evidenced. Those are product/process calls for the human reviewer, not review defects. Net: the generality change is done. The one thing I'd want resolved before approval is FR-016's warning mechanism — it's the only open reviewer thread and it prescribes something GraphQL doesn't hand you for free. · |
nollymar
left a comment
There was a problem hiding this comment.
It seems like the spec is written to try to solve the issue in the provided example, not the issue in general.
The GraphQL API should expose any dotAsset/fileAsset content type so that I can be able to "cast" any binary to that type. And in case it is not possible, I should return the results that could be casted and the warnings for those that couldn't
…4540) Four requirements the first draft left implicit, all verified by introspection against a running instance: - Shared asset properties are selectable both directly on the field and inside a clause that narrows to a specific type (FR-015). - Narrowing to a type the asset does not happen to be must not fail the request; it contributes nothing, and a non-fatal warning names the clause that matched nothing, so "wasn't that type" is distinguishable from "wrong type name" (FR-016). There is no cast here to fail — the clause is a condition. - Narrowing to a type that does not exist must still fail loudly (FR-017). - Clauses for the base kind and for the specific type merge into one result object (FR-018). Also records that the hierarchy is exactly two levels deep and cannot grow: a content type may only extend one of the fixed base types, never another content type. A chain of narrowing clauses therefore does not arise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pushed a second commit pinning down how narrowing clauses behave on an asset field — four requirements the first draft left implicit, each checked by introspection against a running instance. Shared properties are available at both levels (FR-015). A client can select them directly on the field and again inside a clause that narrows to a specific type: image {
identifier # shared, at the top
... on Images { tags } # plus what's specific to this type
}Narrowing to a type the asset is not must not fail the request (FR-016). Worth being precise: there is no cast here to fail. Naming a type that does not exist must still fail (FR-017). No valid reading, so failing loudly is correct. Clauses merge (FR-018). Asking for the base kind and the specific type in one request yields one object, not two partial ones: ... on DotAssetBaseType { asset { size } }
... on Images { tags }
→ {"asset": {"size": 43692}, "tags": []}On hierarchy depth — this came up as an open question and the answer is that it does not arise. Introspection shows the base-kind description sits under nothing, and a specific type names its shared descriptions side by side rather than nested: So the hierarchy is exactly two levels and cannot grow, because a content type may only extend one of the fixed base types and never another content type. A client never faces a chain of narrowing clauses — at most one for the base kind and one for the specific type, and per FR-018 those merge. |
…types (#34540) Addresses @nollymar's change request. Her inline note is about framing, but checking the second half of it turned up a real gap in the proposed shape. Framing (her inline comment on the Overview): - The Overview now leads with "any content type extending either asset base type" and says outright that the banner/PDF cases are illustrations. - Added two acceptance scenarios to User Story 1 stating the general case explicitly, and noting that passing the two example scenarios does not satisfy it. Substance ("expose any dotAsset/fileAsset content type so I can cast any binary to that type"): - FR-001a: the reachable set is every content type extending either base type, including ones created later, and may not be satisfied by enumeration. - FR-001b: an asset-pointing field must span BOTH base types regardless of the field's own kind. Verified on a running instance — an Image field accepts and resolves a reference to file-style content (a plain-text file came back through one). The earlier "shape of the solution" typed each field by its own kind, which would have silently dropped content those fields already hold today. That section is corrected. - FR-016a: the warning behavior holds across a whole result set. Assets that match a clause come back populated, assets that do not still come back, and one non-matching asset must not suppress the matching ones. Also fixes the requirement ordering (FR-014 was listed last). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@nollymar thanks — you were right on both counts, and chasing the second one turned up a real gap in the proposed shape, not just the framing. On the framing. Fair. The normative requirements were already general (FR-001 says "any property the customer defined on the content type of the asset that field points at"), but the Overview and every acceptance scenario were written around the banner/PDF cases, which is what makes it read as example-driven. Fixed: the Overview now leads with "any content type extending either asset base type" and states outright that those two are illustrations. User Story 1 gains two scenarios stating the general case, one of which says explicitly that passing the two example scenarios does not satisfy it. On "cast any binary to that type" — this found a defect. I tested whether an Image field is actually constrained to image-style content. It is not: "image": {
"fileName": "storeProductList.vtl",
"fileAsset": { "mime": "text/plain; charset=ISO-8859-1", "size": 3128 }
}That is an Image field on a real content type, pointing at a FileAsset — a My "shape of the solution" section typed an Image field as the image-style interface and a File field as the file-style one. That would have silently dropped content those fields already hold. Corrected, and pinned as requirements:
On "return the results that could be casted and the warnings for those that couldn't." That is a collection-level statement and FR-016 was written per-asset, so I added FR-016a: across one result set, assets matching a clause come back populated, assets that do not still come back, the request as a whole succeeds, and a single non-matching asset must not suppress the matching ones. Warnings identify which clause matched nothing rather than being one opaque flag. One related thing already in the spec, since it is the same conversation: narrowing to a type that does not exist still fails the request (FR-017). The "must not fail" rule is for a type that exists but is not what this particular asset turned out to be. Say the word if you would rather both be warnings. Pushed as |
|
Correction to my last comment: the final paragraph offered to reconsider whether a non-existent type should warn instead of fail. That is not open — it is already decided. A clause naming a type that does not exist fails the request (FR-017). It is a client mistake with no valid reading, and a silent pass would hand back missing data with no signal. The lenient rule applies only to a type that exists but is not what this particular asset turned out to be (FR-016 / FR-016a). No spec change; disregard that paragraph. |
|
@fabrizzio-dotCMS just to keep in mind: FR-016 and FR-016a require a non-fatal warning per clause that matched nothing, identified by clause. Standard GraphQL has no warnings slot; the realistic homes are the errors array with a path, or extensions |
…4540) The ADR consultation that gates planning turned up a conflict with the decision this spec had already recorded, so the decision is reversed. Two accepted decisions in platform-adrs govern a published contract: - ADR-0020 deprecated a core REST endpoint and kept it functional, annotated for removal, existing integrations explicitly unaffected. - ADR-0022 states that "a URL, parameter, or response field is a promise to callers in exactly the same way a database column is a promise to queries", and names why it bites here: dotCMS is self-hosted and upgraded on the customer's schedule, so "nobody is calling the old endpoint right now" is not the same question as "it's safe to remove." It prescribes expand -> adopt -> bake -> retire, gates removal on a supported-version floor plus a confirmed zero-use window, and requires the deprecation to be marked in the schema itself rather than only in code. So instead of replacing the asset view in place: - FR-012 keeps every existing selection working and returning what it returns today; the new capability ships alongside. - FR-012a marks the current view as superseded in the published API description, naming each property's replacement. - FR-012b puts removal out of scope for this feature, behind the same gate, and requires the retirement ticket to be opened up front. - FR-009a is reframed: rather than renaming the one property that would have changed meaning, the corrected value is reached through the new surface and the existing name is left alone. - SC-008 and SC-009 make the guarantee measurable. The decisive case is unchanged and now argues the other way: the asset description would have kept returning a string while changing which string — 57 of 57 images populated today, 2 of 57 afterwards. A self-hosted customer who had not upgraded would have seen no failure, just different data. Adds an ADR Alignment section. No exception to either ADR is requested. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@nollymar re-review needed — an approved decision in this spec has been reversed. Running the ADR consultation that gates What the ADRs say
What changed in the spec
Plus an ADR Alignment section. No exception to either ADR is requested. Why this is the right way round. The decisive case is the same one and it now argues the other way. The asset description would have kept returning a string while changing which string — populated for 57 of 57 images today, 2 of 57 afterwards. A self-hosted customer who had not upgraded would have seen no failure, just different data. That is precisely the outcome ADR-0022's reasoning exists to prevent. Nothing else changed — the generalization work from your earlier review (FR-001a, FR-001b, FR-016a) is untouched. Pushed as |
PR 1 of 2 — spec only. Carries
spec.mdalone. Approval here gates/speckit-plan; no implementation until then.Refs #34540.
The problem
Querying an Image or File field over GraphQL returns
DotFileasset— a flat, six-property object built once in a static block — instead of the real type of the asset the field points at.So a customer who extends DOTASSET or FILEASSET to model their own assets can author those fields but cannot read them back. The AI tagging workflow is blocked the same way. There is no workaround.
Root cause is
ContentAPIGraphQLTypesProviderlines 97-98, mapping bothImageField.classandFileField.classto the singleCustomFieldType.FILEASSET. The per-content-type generation and theDotAssetBaseType/FileBaseTypeinterfaces already exist and are already dynamic — verified by introspection against a running instance — so the gap is only in how these two field classes are typed.Two decisions the reviewer is being asked to confirm
1. Breaking existing queries is accepted (FR-012).
The current view is a facade: it reports image-style content using property names borrowed from the file-style base type. Measured against a running instance, five of its six properties (
fileName,fileAsset,metaData,showOnMenu,sortOrder) do not exist on the real type at all. Keeping them working means keeping the misdescription forever.Those five break loudly — an outright request failure.
descriptiondoes not, and that is the one to look at: it stays a valid name and keeps returning a string, but a different one. Today it returns the asset title (the file name, populated for 57 of 57 images); afterwards it returns the asset's stored description (populated for 2 of those 57). No error, no log line. FR-009a therefore requires it to be renamed rather than silently repurposed.File-style assets fare better: their properties are real and merely move to the specific type.
2. PR #35363 is superseded, not merged (FR-013).
That PR adds a
DotBinaryLikeinterface flattening 12 binary properties ontoDotFileasset, and states it closes #34540. It delivers only the second limb of the issue; customer-defined fields stay unreachable after it.Its benefit does not survive this feature. It exists to spare one level of nesting —
image { size }overimage { fileAsset { size } }— but once the field returns the asset's real type, that type'sassetproperty is already aDotBinarycarrying all 12 plusfocalPoint. The client still descends exactly one level, just to the correctly named property. Meanwhile its deliverable is attached to the view this feature removes, its checks have been failing since it was last updated on a check in its own area, and it carries no review.The remaining scope is deferred, not dropped: #34540 stays open after this ships, carrying the second stage, whose design depends on the shape this one establishes.
Worth flagging separately
GraphqlAPITest.areFileassetFieldsPresentusesallMatch, so it asserts that the asset view exposes no property other than the six it has today — the opposite of what its name says. It is what #35363's CI trips on, and it will block this work too. FR-014 requires correcting it.Not in this PR
No implementation. Planning starts once this is approved.
🤖 Generated with Claude Code