From fc1a8f5edee7d98502ee032b46e2d06bd730eeca Mon Sep 17 00:00:00 2001 From: Cody Lundie Date: Mon, 24 Aug 2026 09:47:35 -0400 Subject: [PATCH] fix: keep writeOnly fields out of response documentation (#160) Success-response documentation showed the writeOnly Wi-Fi passphrase (securityMode.password) via two independent paths: - Response examples in components/examples reused request-side YAML anchors. Split the anchors into request and response fragments; response fragments omit writeOnly fields. - Object-level `example:` blocks on the AccessDetail schemas embedded the password. An object-level example is context-free and is rendered verbatim into both the request and the response pane, bypassing writeOnly filtering. Removed every object-level example across the modules; renderers now synthesize object samples from property-level examples, which are readOnly/writeOnly aware. Backfilled the property-level examples that the removed blocks were masking. Also in this pass: - Drop the redundant readOnly overlay branches on TrustDomain, TrustDomainDevice, RebootRequest and NetworkAccessDevice. Those fields are already readOnly in ResourceIdentifier/ResourceAudit, and a bare `readOnly: true` stub wins the allOf merge and blanks the property to null in generated samples. TrustDomainDevice keeps the ipv4Address/ipv6Address overrides, which Commonalities Device does not declare readOnly, restated against the underlying schemas so they no longer blank. - Give ResourceIdentifier.id and ResourceAudit.modifiedAt their own property-level examples so synthesized samples stop repeating one literal for id/createdBy and one timestamp for createdAt/modifiedAt. - Remove duplicate example UUIDs that collided as schema identifiers and made redocly's no-invalid-schema-examples abort without validating five schemas. - Delete the unused thread-tlv-example anchor, which carried a `mode` property that ThreadTlvAccessDetail rejects. - Wire the three unreferenced components/examples into operations that had no example: RebootRequestResponseInferred and RebootRequestResponseExplicit on POST /reboot-requests, TrustDomainDeviceCreateAssignCredential on device registration. Reconcile the reboot request/response pairs: the response echoes the request's message verbatim, the immediate (inferred) response carries no atTime, and the scheduled time is later than the audit timestamps. - Add curated two-item list examples for GET /services and GET /network-access-devices, preserving the multi-item narratives the deleted object-level list examples carried (optional serviceSite omitted on one service; connected and disconnected deviceStatus values shown). - Promote no-invalid-media-type-examples and no-invalid-schema-examples to `error` in redocly.yaml. The former is the only check that validates examples with request/response context; readOnly/writeOnly are JSON Schema annotations rather than assertions, so the centralized Spectral pipeline cannot detect this class of defect. The latter validates schema-level examples structurally only. No API shape change. --- .../network-access-devices.yaml | 8 ++ .../network-access-domains.yaml | 5 + code/modules/AccessDetail.yaml | 41 ++------ code/modules/NAM_Common.yaml | 67 +++++-------- .../NetworkAccessDevices.yaml | 94 +++++++------------ code/modules/Policy.yaml | 25 ++--- .../RebootRequests/RebootRequests.yaml | 35 ++++--- code/modules/Services/ServiceSites.yaml | 43 +-------- code/modules/Services/Services.yaml | 58 +++--------- .../TrustDomainDevices.yaml | 30 +++--- .../TrustDomains/TrustDomainCapabilities.yaml | 9 +- code/modules/TrustDomains/TrustDomains.yaml | 54 ++++++----- code/redocly.yaml | 10 ++ 13 files changed, 184 insertions(+), 295 deletions(-) diff --git a/code/API_definitions/network-access-devices.yaml b/code/API_definitions/network-access-devices.yaml index 636acce..076c5fa 100644 --- a/code/API_definitions/network-access-devices.yaml +++ b/code/API_definitions/network-access-devices.yaml @@ -218,6 +218,9 @@ paths: application/json: schema: $ref: "../modules/NetworkAccessDevices/NetworkAccessDevices.yaml#/components/schemas/NetworkAccessDeviceList" + examples: + NetworkAccessDeviceListResponse: + $ref: "../modules/NetworkAccessDevices/NetworkAccessDevices.yaml#/components/examples/NetworkAccessDeviceListResponse" "400": $ref: "../common/CAMARA_common.yaml#/components/responses/Generic400" "401": @@ -334,6 +337,11 @@ paths: application/json: schema: $ref: '../modules/RebootRequests/RebootRequests.yaml#/components/schemas/RebootRequest' + examples: + RebootRequestResponseInferred: + $ref: '../modules/RebootRequests/RebootRequests.yaml#/components/examples/RebootRequestResponseInferred' + RebootRequestResponseExplicit: + $ref: '../modules/RebootRequests/RebootRequests.yaml#/components/examples/RebootRequestResponseExplicit' '400': $ref: '../common/CAMARA_common.yaml#/components/responses/Generic400' '401': diff --git a/code/API_definitions/network-access-domains.yaml b/code/API_definitions/network-access-domains.yaml index 8a1213b..4e511a8 100644 --- a/code/API_definitions/network-access-domains.yaml +++ b/code/API_definitions/network-access-domains.yaml @@ -250,6 +250,9 @@ paths: application/json: schema: $ref: "../modules/Services/Services.yaml#/components/schemas/ServiceList" + examples: + ServiceListResponse: + $ref: "../modules/Services/Services.yaml#/components/examples/ServiceListResponse" "400": $ref: "../common/CAMARA_common.yaml#/components/responses/Generic400" "401": @@ -622,6 +625,8 @@ paths: $ref: '../modules/TrustDomainDevices/TrustDomainDevices.yaml#/components/examples/TrustDomainDeviceCreateMinimal' TrustDomainDeviceCreateMatter: $ref: '../modules/TrustDomainDevices/TrustDomainDevices.yaml#/components/examples/TrustDomainDeviceCreateMatter' + TrustDomainDeviceCreateAssignCredential: + $ref: '../modules/TrustDomainDevices/TrustDomainDevices.yaml#/components/examples/TrustDomainDeviceCreateAssignCredential' responses: '201': headers: diff --git a/code/modules/AccessDetail.yaml b/code/modules/AccessDetail.yaml index 2c8ac69..8cfdb76 100644 --- a/code/modules/AccessDetail.yaml +++ b/code/modules/AccessDetail.yaml @@ -1,5 +1,11 @@ # This file defines the AccessDetail schema model for different network access types. # It includes Wi-Fi and Thread access details with various security modes. +# +# Examples: property-level only. No object-level `example:` blocks here — several of +# these schemas carry writeOnly secrets, and an object-level example is printed +# verbatim into the response pane, defeating the writeOnly filter. Renderers +# synthesize object samples from the property examples below and honor +# readOnly/writeOnly while doing so. components: schemas: @@ -15,7 +21,7 @@ components: maxLength: 255 pattern: "^[\\x20-\\x7E]{8,63}$" description: The password for the WPA Personal Wi-Fi network - example: &wpa-personal-password "my-password" + example: "my-password" securityModeType: type: string enum: @@ -25,13 +31,10 @@ components: description: | The security mode type for the WPA Personal Wi-Fi network. Leave blank to auto-select. - example: &wpa-personal-security-mode-type "WPA3-Personal" + example: "WPA3-Personal" additionalProperties: false required: - password - example: &wpa-personal - password: *wpa-personal-password - securityModeType: *wpa-personal-security-mode-type WpaEnterpriseDetail: type: object @@ -41,20 +44,17 @@ components: type: string maxLength: 255 description: The authentication server for the WPA Enterprise Wi-Fi network - example: &wpa-enterprise-auth-server "1.2.3.4" + example: "1.2.3.4" securityModeType: type: string enum: - WPA2-Enterprise - WPA3-Enterprise description: The security mode type for the WPA Enterprise Wi-Fi network - example: &wpa-enterprise-security-mode-type "WPA3-Enterprise" + example: "WPA3-Enterprise" additionalProperties: false required: - securityModeType - example: &wpa-enterprise - authServer: *wpa-enterprise-auth-server - securityModeType: *wpa-enterprise-security-mode-type # Flattened concrete implementations for Wi-Fi WiFiWpaPersonalAccessDetail: @@ -83,15 +83,10 @@ components: allOf: - $ref: "#/components/schemas/WpaPersonalDetail" description: WPA Personal security mode details - example: *wpa-personal additionalProperties: false required: - accessType - securityMode - example: &wifi-wpa-personal-access-detail - accessType: "Wi-Fi:WPA_PERSONAL" - ssid: *wifi-ssid - securityMode: *wpa-personal WiFiWpaEnterpriseAccessDetail: type: object @@ -119,15 +114,10 @@ components: allOf: - $ref: "#/components/schemas/WpaEnterpriseDetail" description: WPA Enterprise security mode details - example: *wpa-enterprise additionalProperties: false required: - accessType - securityMode - example: &wifi-wpa-enterprise-access-detail - accessType: "Wi-Fi:WPA_ENTERPRISE" - ssid: *wifi-ssid - securityMode: *wpa-enterprise ThreadStructuredAccessDetail: type: object @@ -178,13 +168,6 @@ components: - networkKey - networkName - panId - example: &thread-structured-access-detail - accessType: "Thread:STRUCTURED" - channel: 13 - extendedPanId: "d63e8e3e495ebbc3" - networkKey: "dfd34f0f05cad978ec4e32b0413038ff" - networkName: "Spec-Thread-B3AF" - panId: "d63e" ThreadTlvAccessDetail: type: object @@ -205,9 +188,6 @@ components: required: - accessType - operationalDataset - example: &thread-tlv-access-detail - accessType: "Thread:TLV" - operationalDataset: "0e08000000000000010010000102030405060708090a0b0c0d0e0f" # Single discriminated union with namespaced values AccessDetail: @@ -226,4 +206,3 @@ components: required: - accessType description: Network access details for different network types and variants - example: *wifi-wpa-personal-access-detail diff --git a/code/modules/NAM_Common.yaml b/code/modules/NAM_Common.yaml index c4a5d88..d547c51 100644 --- a/code/modules/NAM_Common.yaml +++ b/code/modules/NAM_Common.yaml @@ -41,7 +41,7 @@ components: It must follow [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) and must have time zone. Recommended format is yyyy-MM-dd'T'HH:mm:ss.SSSZ (i.e. which allows 2023-07-03T14:27:08.312+02:00 or 2023-07-03T12:27:08.312Z) - example: &date-time "2023-07-03T14:27:08.312+02:00" + example: "2023-07-03T14:27:08.312+02:00" PropertyAddress: type: object @@ -55,123 +55,104 @@ components: description: The unique identifier for the property address minLength: 1 maxLength: 64 - example: &property-address-id "address-123" + example: "address-123" addressLine1: type: string maxLength: 128 description: The first line of the address - example: &property-address-address-line-1 "123 Main St" + example: "123 Main St" addressLine2: type: string maxLength: 128 description: The second line of the address - example: &property-address-address-line-2 "Suite 456" + example: "Suite 456" streetNr: type: string maxLength: 64 description: The street number - example: &property-address-street-nr "123" + example: "123" streetNrSuffix: type: string maxLength: 64 description: The suffix for the street number - example: &property-address-street-nr-suffix "A" + example: "A" streetNrLast: type: string maxLength: 64 description: The last street number - example: &property-address-street-nr-last "125" + example: "125" streetNrLastSuffix: type: string maxLength: 64 description: The suffix for the last street number - example: &property-address-street-nr-last-suffix "B" + example: "B" streetName: type: string maxLength: 64 description: The name of the street - example: &property-address-street-name "Main" + example: "Main" subUnitType: type: string maxLength: 64 description: The type of sub-unit (e.g., Apartment, Suite) - example: &property-address-sub-unit-type "Apartment" + example: "Apartment" subUnitNr: type: string maxLength: 64 description: The number of the sub-unit - example: &property-address-sub-unit-nr "A12" + example: "A12" levelType: type: string maxLength: 64 description: The type of level (e.g., Floor, Basement) - example: &property-address-level-type "Floor" + example: "Floor" levelNr: type: string maxLength: 64 description: The number of the level - example: &property-address-level-nr "3" + example: "3" buildingName: type: string maxLength: 64 description: The name of the building - example: &property-address-building-name "Example Building" + example: "Example Building" privateStreetName: type: string maxLength: 64 description: The name of a private street - example: &property-address-private-street-name "Private Rd" + example: "Private Rd" locality: type: string maxLength: 64 description: The locality (neighborhood, area) - example: &property-address-locality "Example Locality" + example: "Example Locality" city: type: string maxLength: 128 description: The city or town - example: &property-address-city "Example State" + example: "Example State" stateOrProvince: type: string maxLength: 64 description: The state or province - example: &property-address-state-or-province "EX" + example: "EX" country: type: string maxLength: 64 description: The country - example: &property-address-country "United States" + example: "United States" postCode: type: string maxLength: 64 description: The postal code or ZIP code - example: &property-address-post-code "12345" + example: "12345" postCodeExtension: type: string maxLength: 64 description: The postal code extension - example: &property-address-post-code-extension "6789" - example: &property-address - id: *property-address-id - addressLine1: *property-address-address-line-1 - addressLine2: *property-address-address-line-2 - streetNr: *property-address-street-nr - streetNrSuffix: *property-address-street-nr-suffix - streetNrLast: *property-address-street-nr-last - streetNrLastSuffix: *property-address-street-nr-last-suffix - streetName: *property-address-street-name - subUnitType: *property-address-sub-unit-type - subUnitNr: *property-address-sub-unit-nr - levelType: *property-address-level-type - levelNr: *property-address-level-nr - buildingName: *property-address-building-name - privateStreetName: *property-address-private-street-name - locality: *property-address-locality - city: *property-address-city - stateOrProvince: *property-address-state-or-province - country: *property-address-country - postCode: *property-address-post-code - postCodeExtension: *property-address-post-code-extension + example: "6789" + # No object-level example: every property above carries one, and renderers + # synthesize the object sample from those. ResourceIdentifier: type: object @@ -180,6 +161,7 @@ components: allOf: - $ref: "#/components/schemas/Uuid" description: Unique identifier of the resource. + example: "3fa85f64-5717-4562-b3fc-2c963f66afa6" readOnly: true required: - id @@ -210,6 +192,7 @@ components: The last update date and time of the resource. It must follow [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) and must have time zone. Recommended format is yyyy-MM-dd'T'HH:mm:ss.SSSZ (i.e. which allows 2023-07-03T14:27:08.312+02:00 or 2023-07-03T12:27:08.312Z). + example: "2023-07-06T09:15:22.104+02:00" readOnly: true modifiedBy: allOf: diff --git a/code/modules/NetworkAccessDevices/NetworkAccessDevices.yaml b/code/modules/NetworkAccessDevices/NetworkAccessDevices.yaml index eab7d96..a6481de 100644 --- a/code/modules/NetworkAccessDevices/NetworkAccessDevices.yaml +++ b/code/modules/NetworkAccessDevices/NetworkAccessDevices.yaml @@ -1,36 +1,5 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. -_imported_examples: - uuid-example: &uuid-example "3fa85f64-5717-4562-b3fc-2c963f66afa6" - uuid-example-2: &uuid-example-2 "123e4567-e89b-12d3-a456-426614174000" - property-address-example: &property-address-example - id: *uuid-example - addressLine1: "123 Main St" - addressLine2: "Suite 456" - streetNr: "123" - streetNrSuffix: "A" - streetNrLast: "125" - streetNrLastSuffix: "B" - streetName: "Main" - subUnitType: "Apartment" - subUnitNr: "A12" - levelType: "Floor" - levelNr: "3" - buildingName: "Sunset Towers" - privateStreetName: "Private Rd" - locality: "Example Locality" - city: "Example State" - stateOrProvince: "EX" - country: "United States" - postCode: "12345" - postCodeExtension: "6789" - service-site-example: &service-site-example - id: "3fa85f64-5717-4562-b3fc-2c963f66afa6" - name: "Primary Residence" - description: "123 Main Street" - location: *property-address-example - - +# Examples: property-level only. Renderers synthesize the object and list samples from +# those and honor readOnly/writeOnly while doing so. components: parameters: networkAccessDeviceId: @@ -53,22 +22,23 @@ components: allOf: - $ref: "../NAM_Common.yaml#/components/schemas/ResourceIdentifier" - $ref: "../../common/CAMARA_common.yaml#/components/schemas/Device" + # No readOnly overlay for `id`: ResourceIdentifier.id already declares it, and a + # bare `readOnly: true` stub wins the allOf merge and blanks the property to null + # in generated samples. - type: object properties: - id: - readOnly: true name: type: string minLength: 1 maxLength: 100 description: The name of the device - example: &device-name "Gateway-1" + example: "Gateway-1" description: type: string minLength: 0 maxLength: 255 description: A description of the device - example: &device-description "A Wi-Fi gateway device" + example: "A Wi-Fi gateway device" hardwareAddress: description: The hardware address of the device oneOf: @@ -77,20 +47,17 @@ components: hardwareAddressType: type: string enum: ["EUI-48"] - example: &device-hardware-address-type "EUI-48" + example: "EUI-48" value: type: string pattern: "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" maxLength: 17 - example: &device-hardware-address-value "12:34:56:78:9A:BC" + example: "12:34:56:78:9A:BC" required: - hardwareAddressType - value discriminator: propertyName: hardwareAddressType - example: &device-hardware-address - hardwareAddressType: *device-hardware-address-type - value: *device-hardware-address-value serviceSite: $ref: "../Services/ServiceSites.yaml#/components/schemas/ServiceSite" deviceStatus: @@ -101,16 +68,7 @@ components: - unavailable default: unavailable description: The current status of the device - example: &device-status "connected" - required: - - id - example: &device - id: *uuid-example - deviceStatus: *device-status - name: *device-name - description: *device-description - hardwareAddress: *device-hardware-address - serviceSite: *service-site-example + example: "connected" NetworkAccessDeviceList: type: array @@ -118,13 +76,33 @@ components: items: $ref: "#/components/schemas/NetworkAccessDevice" maxItems: 100 - example: &device-list - - *device - - id: *uuid-example-2 - deviceStatus: "disconnected" + + examples: + NetworkAccessDeviceListResponse: + summary: List of network access devices + description: | + Two operator-supplied devices at the same service site, one connected and one + disconnected. + value: + - id: "3fa85f64-5717-4562-b3fc-2c963f66afa6" + name: "Gateway-1" + description: "A Wi-Fi gateway device" + deviceStatus: "connected" + hardwareAddress: + hardwareAddressType: "EUI-48" + value: "12:34:56:78:9A:BC" + serviceSite: + id: "123e4567-e89b-12d3-a456-426614174000" + name: "Primary Residence" + description: "123 Main Street" + - id: "123e4567-e89b-12d3-a456-426614174001" name: "Gateway-2" description: "Another Wi-Fi gateway device" - hardwareAddress: &device-hardware-address2 + deviceStatus: "disconnected" + hardwareAddress: hardwareAddressType: "EUI-48" value: "AB:CD:EF:12:34:56" - serviceSite: *service-site-example + serviceSite: + id: "123e4567-e89b-12d3-a456-426614174000" + name: "Primary Residence" + description: "123 Main Street" diff --git a/code/modules/Policy.yaml b/code/modules/Policy.yaml index 637f394..42df750 100644 --- a/code/modules/Policy.yaml +++ b/code/modules/Policy.yaml @@ -1,5 +1,9 @@ # This file defines the Policies schema model that describe the various policies applicable to # Trust Domains. +# +# Examples: property-level only for object schemas. Renderers synthesize the object sample +# from those. An `example:` on an array of scalars is kept — the array is the leaf, there +# are no properties beneath it to synthesize from. components: schemas: @@ -16,17 +20,6 @@ components: egressAllowedList: $ref: '#/components/schemas/EgressAllowedListPolicy' additionalProperties: false - example: - maxDevices: 10 - maxDomainDownstreamRate: - value: 500000 - unit: Kbps - maxDomainUpstreamRate: - value: 10000 - unit: Kbps - egressAllowedList: - - "api.vendor.com" - - "updates.vendor.com" MaxDevicesPolicy: type: integer @@ -45,17 +38,16 @@ components: minimum: 0 maximum: 10000000 description: Maximum downstream throughput across the domain. + example: 1000000 unit: type: string enum: [Kbps, Mbps, Gbps] description: Unit of measurement for downstream throughput. + example: Kbps required: - value - unit description: Maximum downstream throughput policy across the Trust Domain. - example: - value: 1000000 - unit: Kbps MaxDomainUpstreamRatePolicy: type: object @@ -66,17 +58,16 @@ components: minimum: 0 maximum: 10000000 description: Maximum upstream throughput across the domain. + example: 500000 unit: type: string enum: [Kbps, Mbps, Gbps] description: Unit of measurement for upstream throughput. + example: Kbps required: - value - unit description: Maximum upstream throughput policy across the Trust Domain. - example: - value: 500000 - unit: Kbps EgressAllowedListPolicy: type: array diff --git a/code/modules/RebootRequests/RebootRequests.yaml b/code/modules/RebootRequests/RebootRequests.yaml index 1eb4a89..d4c26da 100644 --- a/code/modules/RebootRequests/RebootRequests.yaml +++ b/code/modules/RebootRequests/RebootRequests.yaml @@ -1,9 +1,11 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. +# Hand-maintained example fragments for the media-type examples below. OpenAPI cannot $ref +# an example value across files, so these anchors are duplicated data and must be updated +# by hand when the schemas change. Schemas themselves carry property-level examples only. _imported_examples: uuid-example: &uuid-example-1 "3fa85f64-5717-4562-b3fc-2c963f66afa6" uuid-example-2: &uuid-example-2 "123e4567-e89b-12d3-a456-426614174000" date-time: &date-time "2023-07-03T12:27:08.312Z" + scheduled-at: &scheduled-at "2023-07-10T02:00:00.000Z" device-uuid-1: &device-uuid-1 "934ee3f4-1234-5678-9abc-abcdef123456" device-uuid-2: &device-uuid-2 "454ee3f4-1234-5678-9abc-abcdef123456" device-id-list: &device-id-list @@ -31,7 +33,7 @@ components: type: string maxLength: 255 description: A message related to the reboot request - example: &reboot-message "reboot to correct performance" + example: "reboot to correct performance" atTime: allOf: - $ref: "../NAM_Common.yaml#/components/schemas/DateTime" @@ -61,18 +63,8 @@ components: - $ref: "../NAM_Common.yaml#/components/schemas/ResourceIdentifier" - $ref: "#/components/schemas/RebootRequestCreate" - $ref: "../NAM_Common.yaml#/components/schemas/ResourceAudit" - - type: object - properties: - id: - readOnly: true - createdAt: - readOnly: true - createdBy: - readOnly: true - modifiedAt: - readOnly: true - modifiedBy: - readOnly: true + # No readOnly overlay: ResourceIdentifier.id and every ResourceAudit field already + # declare readOnly, and a bare stub blanks the property to null in generated samples. required: - devices description: Represents a reboot request for one or more network access devices. @@ -95,17 +87,19 @@ components: description: Request a scheduled reboot of multiple explicitly listed devices at a future 'atTime'. value: message: "Scheduled reboot for maintenance" - atTime: *date-time + atTime: *scheduled-at devices: *device-id-list RebootRequestResponseInferred: summary: Response after inferred default device reboot request + description: | + Reboot Request created from a body that omitted 'devices'. The response reflects the + single default device the provider resolved. value: id: *uuid-example-1 message: "reboot to correct performance" devices: - *device-uuid-1 - atTime: *date-time createdAt: *date-time createdBy: *created-by-example modifiedAt: *date-time @@ -113,11 +107,14 @@ components: RebootRequestResponseExplicit: summary: Response after explicit multi-device reboot request + description: | + Reboot Request created from a body that listed device IDs explicitly and scheduled + the reboot with 'atTime'. value: id: *uuid-example-2 - message: "scheduled maintenance reboot" + message: "Scheduled reboot for maintenance" devices: *device-id-list - atTime: *date-time + atTime: *scheduled-at createdAt: *date-time createdBy: *created-by-example modifiedAt: *date-time diff --git a/code/modules/Services/ServiceSites.yaml b/code/modules/Services/ServiceSites.yaml index 37911e9..d6dae5b 100644 --- a/code/modules/Services/ServiceSites.yaml +++ b/code/modules/Services/ServiceSites.yaml @@ -1,30 +1,5 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. -_imported_examples: - uuid-example: &uuid-example "3fa85f64-5717-4562-b3fc-2c963f66afa6" - uuid-example-2: &uuid-example-2 "123e4567-e89b-12d3-a456-426614174000" - property-address-example: &property-address-example - id: *uuid-example - addressLine1: "123 Main St" - addressLine2: "Suite 456" - streetNr: "123" - streetNrSuffix: "A" - streetNrLast: "125" - streetNrLastSuffix: "B" - streetName: "Main" - subUnitType: "Apartment" - subUnitNr: "A12" - levelType: "Floor" - levelNr: "3" - buildingName: "Sunset Towers" - privateStreetName: "Private Rd" - locality: "Example Locality" - city: "Example State" - stateOrProvince: "EX" - country: "United States" - postCode: "12345" - postCodeExtension: "6789" - +# Examples: property-level only. Renderers synthesize the object samples from those and +# honor readOnly/writeOnly while doing so. components: schemas: ServiceSite: @@ -40,12 +15,12 @@ components: type: string description: The name of the service site. maxLength: 64 - example: &service-site-name "Example Service Site 1" + example: "Example Service Site 1" description: type: string description: A textual description of the service site. maxLength: 64 - example: &service-site-description "This is the first example service site" + example: "This is the first example service site" location: type: object description: Geographic location information for the service site, including coordinates and/or property address. @@ -54,15 +29,5 @@ components: $ref: "../../common/CAMARA_common.yaml#/components/schemas/Point" propertyAddress: $ref: "../NAM_Common.yaml#/components/schemas/PropertyAddress" - example: &service-site-location - geographicPoint: - latitude: 50.735851 - longitude: 7.10066 - propertyAddress: *property-address-example required: - id - example: &service-site - id: *uuid-example - name: *service-site-name - description: *service-site-description - location: *service-site-location diff --git a/code/modules/Services/Services.yaml b/code/modules/Services/Services.yaml index 9d981ba..99a7190 100644 --- a/code/modules/Services/Services.yaml +++ b/code/modules/Services/Services.yaml @@ -1,36 +1,5 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. -_imported_examples: - uuid-example: &uuid-example "3fa85f64-5717-4562-b3fc-2c963f66afa6" - uuid-example-2: &uuid-example-2 "123e4567-e89b-12d3-a456-426614174000" - uuid-example-3: &uuid-example-3 "123e4567-e89b-12d3-a456-426614174001" - property-address-example: &property-address-example - id: *uuid-example - addressLine1: "123 Main St" - addressLine2: "Suite 456" - streetNr: "123" - streetNrSuffix: "A" - streetNrLast: "125" - streetNrLastSuffix: "B" - streetName: "Main" - subUnitType: "Apartment" - subUnitNr: "A12" - levelType: "Floor" - levelNr: "3" - buildingName: "Sunset Towers" - privateStreetName: "Private Rd" - locality: "Example Locality" - city: "Example State" - stateOrProvince: "EX" - country: "United States" - postCode: "12345" - postCodeExtension: "6789" - service-site-example: &service-site-example - id: "3fa85f64-5717-4562-b3fc-2c963f66afa6" - name: "Primary Residence" - description: "123 Main Street" - location: *property-address-example - +# Examples: property-level only. Renderers synthesize the object and list samples from +# those and honor readOnly/writeOnly while doing so. components: parameters: serviceId: @@ -74,14 +43,6 @@ components: $ref: "./ServiceSites.yaml#/components/schemas/ServiceSite" required: - id - example: - id: *uuid-example - name: "Main Home Internet" - description: "Internet subscription for primary residence" - serviceSite: - id: *uuid-example-2 - name: "Primary Residence" - description: "123 Main Street" ServiceList: type: array @@ -89,14 +50,21 @@ components: items: $ref: "#/components/schemas/Service" maxItems: 100 - example: - - id: *uuid-example + + examples: + ServiceListResponse: + summary: List of services + description: | + Two services for the calling identity. The first is tied to a service site; the + second omits the optional 'serviceSite' (a service need not be location-specific). + value: + - id: "3fa85f64-5717-4562-b3fc-2c963f66afa6" name: "Main Home Internet" description: "Internet subscription for primary residence" serviceSite: - id: *uuid-example-2 + id: "123e4567-e89b-12d3-a456-426614174000" name: "Primary Residence" description: "123 Main Street" - - id: *uuid-example-3 + - id: "123e4567-e89b-12d3-a456-426614174001" name: "Vacation Home Internet" description: "Internet subscription for vacation home" diff --git a/code/modules/TrustDomainDevices/TrustDomainDevices.yaml b/code/modules/TrustDomainDevices/TrustDomainDevices.yaml index 34a42b8..0062174 100644 --- a/code/modules/TrustDomainDevices/TrustDomainDevices.yaml +++ b/code/modules/TrustDomainDevices/TrustDomainDevices.yaml @@ -1,5 +1,9 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. +# Hand-maintained example fragments for the media-type examples below. OpenAPI cannot $ref +# an example value across files, so these anchors are duplicated data and must be updated +# by hand when the schemas change. Schemas themselves carry property-level examples only. +# +# `device-credential-assign-example` carries the writeOnly `value` and is therefore a +# REQUEST-only fragment — never reuse it in a response example. _imported_examples: device-uuid-example: &device-uuid-example "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" device-uuid-example-2: &device-uuid-example-2 "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" @@ -384,21 +388,20 @@ components: - $ref: "#/components/schemas/TrustDomainDeviceCreate" - $ref: "../../common/CAMARA_common.yaml#/components/schemas/Device" - $ref: "../NAM_Common.yaml#/components/schemas/ResourceAudit" + # No stubs for id / createdAt / createdBy / modifiedAt / modifiedBy: those are + # already readOnly in ResourceIdentifier and ResourceAudit. A bare `readOnly: true` + # stub wins the allOf merge and blanks the property to null in generated samples. + # ipv4Address / ipv6Address DO need the override (Commonalities Device does not mark + # them readOnly), so they restate the underlying schema rather than being bare stubs. - type: object properties: - id: - readOnly: true ipv4Address: + allOf: + - $ref: "../../common/CAMARA_common.yaml#/components/schemas/DeviceIpv4Address" readOnly: true ipv6Address: - readOnly: true - createdAt: - readOnly: true - createdBy: - readOnly: true - modifiedAt: - readOnly: true - modifiedBy: + allOf: + - $ref: "../../common/CAMARA_common.yaml#/components/schemas/DeviceIpv6Address" readOnly: true connected: type: boolean @@ -467,6 +470,9 @@ components: TrustDomainDeviceCreateAssignCredential: summary: Register a device with a client-provided credential. + description: | + Registers a device using ASSIGN, supplying the credential value directly. The + supplied 'value' is writeOnly and is never echoed back in the response. value: deviceName: "Office Switch" externalId: "SW-OFFICE-01" diff --git a/code/modules/TrustDomains/TrustDomainCapabilities.yaml b/code/modules/TrustDomains/TrustDomainCapabilities.yaml index 88f762a..3e9f4a5 100644 --- a/code/modules/TrustDomains/TrustDomainCapabilities.yaml +++ b/code/modules/TrustDomains/TrustDomainCapabilities.yaml @@ -1,3 +1,5 @@ +# Examples: property-level only. Renderers synthesize object samples from those and +# honor readOnly/writeOnly while doing so. components: schemas: TrustDomainCapabilities: @@ -22,13 +24,6 @@ components: $ref: "#/components/schemas/BandwidthPolicyCapability" egressAllowedList: $ref: "#/components/schemas/EgressPolicyCapability" - example: - maxDevices: - maxValue: 100 - minValue: 1 - maxDomainDownstreamRate: - maxValue: 1000000000 - minValue: 1000000 required: - supportedAccessTypes description: Describes the Trust Domain configuration capabilities supported by this API provider diff --git a/code/modules/TrustDomains/TrustDomains.yaml b/code/modules/TrustDomains/TrustDomains.yaml index 14873f1..b83279f 100644 --- a/code/modules/TrustDomains/TrustDomains.yaml +++ b/code/modules/TrustDomains/TrustDomains.yaml @@ -1,14 +1,16 @@ -# Local anchor definitions for external component examples. There isn't a native OpenAPI way to -# cross-reference examples across files, so we define them here for reuse. +# Hand-maintained example fragments for the media-type examples below. OpenAPI cannot +# $ref an example value across files, so these anchors are duplicated data — nothing ties +# them to the schemas they illustrate. They must be updated by hand when those schemas +# change. +# +# Request and response fragments are kept SEPARATE on purpose: reusing one anchor for both +# leaks writeOnly fields into the response documentation. Response fragments must omit every +# writeOnly field; request fragments must omit every readOnly field. _imported_examples: uuid-example: &uuid-example "3fa85f64-5717-4562-b3fc-2c963f66afa6" service-uuid-example: &service-uuid-example "123e4567-e89b-12d3-a456-426614174000" - wifi-wpa2-personal-basic-example: &wifi-wpa2-personal-basic-example - accessType: "Wi-Fi:WPA_PERSONAL" - securityMode: - password: "my-password" - securityModeType: "WPA2-Personal" + # --- request fragments (may carry writeOnly fields such as securityMode.password) --- wifi-wpa2-personal-example: &wifi-wpa2-personal-example accessType: "Wi-Fi:WPA_PERSONAL" ssid: "my-ssid" @@ -23,6 +25,9 @@ _imported_examples: password: "my-password" securityModeType: "WPA3-Personal" + # Shared by request and response examples only because no Thread field is writeOnly. + # If networkKey ever becomes writeOnly, split this into separate request and response + # fragments like the Wi-Fi ones below. thread-structured-example: &thread-structured-example accessType: "Thread:STRUCTURED" channel: 13 @@ -31,10 +36,18 @@ _imported_examples: networkName: "Spec-Thread-B3AF" panId: "d63e" - thread-tlv-example: &thread-tlv-example - accessType: "Thread:TLV" - mode: tlv - operationalDataset: "0e08000000000000010010000102030405060708090a0b0c0d0e0f" + # --- response fragments (writeOnly fields omitted — the server never returns them) --- + wifi-wpa2-personal-response-example: &wifi-wpa2-personal-response-example + accessType: "Wi-Fi:WPA_PERSONAL" + ssid: "my-ssid" + securityMode: + securityModeType: "WPA2-Personal" + + wifi-wpa3-personal-response-example: &wifi-wpa3-personal-response-example + accessType: "Wi-Fi:WPA_PERSONAL" + ssid: "my-ssid" + securityMode: + securityModeType: "WPA3-Personal" max-devices-policy-example: &max-devices-policy-example maxDevices: 10 @@ -155,18 +168,9 @@ components: - $ref: "../NAM_Common.yaml#/components/schemas/ResourceIdentifier" - $ref: "#/components/schemas/TrustDomainCreate" - $ref: "../NAM_Common.yaml#/components/schemas/ResourceAudit" - - type: object - properties: - id: - readOnly: true - createdAt: - readOnly: true - createdBy: - readOnly: true - modifiedAt: - readOnly: true - modifiedBy: - readOnly: true + # No readOnly overlay here: ResourceIdentifier.id and every ResourceAudit field + # already declare readOnly. A bare `readOnly: true` stub wins the allOf merge and + # blanks the property to null in generated samples. description: Represents a trust domain within the Network Access Domains API. @@ -245,7 +249,7 @@ components: description: "A basic trust domain example" enabled: true accessDetails: - - <<: *wifi-wpa2-personal-example + - <<: *wifi-wpa2-personal-response-example serviceId: *service-uuid-example createdAt: "2025-10-21T14:27:08.312Z" createdBy: *created-by-example @@ -264,7 +268,7 @@ components: policies: <<: *egress-allowed-list-policy-example accessDetails: - - <<: *wifi-wpa3-personal-example + - <<: *wifi-wpa3-personal-response-example - <<: *thread-structured-example serviceId: *service-uuid-example createdAt: "2025-10-21T14:27:08.312Z" diff --git a/code/redocly.yaml b/code/redocly.yaml index c653190..14460ee 100644 --- a/code/redocly.yaml +++ b/code/redocly.yaml @@ -4,6 +4,16 @@ extends: rules: no-unused-components: "off" no-empty-servers: "off" + # Promoted from warning to error. no-invalid-media-type-examples is the only check + # that validates examples with request/response context (writeOnly must not appear in + # a response example, readOnly must not appear in a request example); the centralized + # CAMARA Spectral pipeline cannot detect this class of defect because readOnly/writeOnly + # are JSON Schema annotations rather than assertions. no-invalid-schema-examples + # validates schema-level `example:` blocks structurally only — it has no + # request/response context, which is why the modules define examples at the property + # level and carry no object-level `example:` blocks at all. + no-invalid-media-type-examples: error + no-invalid-schema-examples: error resolve: file: