Description
The content-signing save path validates stored formatting attributes against the per-operation connector schemas (ContentSigningFormattingAttributes.aggregate, #1974), but no public endpoint exposes that aggregate, so a client cannot learn which attributes exist before submitting them. The existing GET /v1/signingProfiles/signatureFormattingConnectors/{connectorUuid}/formattingAttributes serves the legacy timestamping contract only, and its OpenAPI description now says so. OmniTrustILM/fe-administrator#1997 is already specified against the merged descriptor set, re-fetched when the operator changes the level maximum.
The contract rule: discovery is the save-time aggregation, exposed read-only. Same code, same inputs, same failure modes, so a form built from discovery always validates on save.
- New route on the signing-profile API, a sibling of the legacy one:
GET /v1/signingProfiles/signatureFormattingConnectors/{connectorUuid}/contentSigningFormattingAttributes?family=...&maxLevel=..., returning the merged flat List<BaseAttribute>. The contract addition lands in interfaces (SigningProfileController). A second endpoint, not a parameter on the legacy route - mirroring the connector-contract split keeps each endpoint on one contract with no default to guess.
family and maxLevel are required because both are form state, not stored state, at the only moments discovery is called: on create no profile exists, and on update the operator is mid-edit changing them. The save path has no default for either, so discovery must not invent one. signingProfileUuid stays optional and authorization-only, matching the legacy route.
- The answer is the save-time aggregate: the operations reachable under
maxLevel, merged by name into one flat set with the definition-consistency guard. Discovery reuses the save predicates rather than forking them - a connector declaring one name with two definitions, a connector not advertising the family, or a maxLevel above the connector's declared per-rung ceiling for that family (DP-16) all fail discovery with the same errors profile save would produce, so the operator learns at form-build time, not at save.
- If the save-time aggregation ever grows an input, discovery grows it in lockstep.
Definition of Done
Description
The content-signing save path validates stored formatting attributes against the per-operation connector schemas (
ContentSigningFormattingAttributes.aggregate, #1974), but no public endpoint exposes that aggregate, so a client cannot learn which attributes exist before submitting them. The existingGET /v1/signingProfiles/signatureFormattingConnectors/{connectorUuid}/formattingAttributesserves the legacy timestamping contract only, and its OpenAPI description now says so. OmniTrustILM/fe-administrator#1997 is already specified against the merged descriptor set, re-fetched when the operator changes the level maximum.The contract rule: discovery is the save-time aggregation, exposed read-only. Same code, same inputs, same failure modes, so a form built from discovery always validates on save.
GET /v1/signingProfiles/signatureFormattingConnectors/{connectorUuid}/contentSigningFormattingAttributes?family=...&maxLevel=..., returning the merged flatList<BaseAttribute>. The contract addition lands ininterfaces(SigningProfileController). A second endpoint, not a parameter on the legacy route - mirroring the connector-contract split keeps each endpoint on one contract with no default to guess.familyandmaxLevelare required because both are form state, not stored state, at the only moments discovery is called: on create no profile exists, and on update the operator is mid-edit changing them. The save path has no default for either, so discovery must not invent one.signingProfileUuidstays optional and authorization-only, matching the legacy route.maxLevel, merged by name into one flat set with the definition-consistency guard. Discovery reuses the save predicates rather than forking them - a connector declaring one name with two definitions, a connector not advertising the family, or amaxLevelabove the connector's declared per-rung ceiling for that family (DP-16) all fail discovery with the same errors profile save would produce, so the operator learns at form-build time, not at save.Definition of Done
interfacescontract, wired to the same aggregation code the save path uses - no second merge implementationfamilyandmaxLevelrequired; name-collision, family and DP-16 ceiling violations rejected with the same errors as profile savesigningProfileUuidauthorization-onlyformattingAttributesroute is left with no content-signing caller: until this endpoint exists, a content-signing caller of the legacy route gets a timestamping-shaped descriptor set (connector declaring both interfaces) or an opaque connector error (content-signing-only connector), and core is knowingly left that way rather than made to refuse - this endpoint plus Content-signing workflow screens fe-administrator#1997 is what ends it