Skip to content

Ignore write-only properties in schema-aware diffs - #1674

Open
Steve Lee (SteveL-MSFT) wants to merge 4 commits into
mainfrom
stevel-msft-ignore-instructed-schema-diffs
Open

Ignore write-only properties in schema-aware diffs#1674
Steve Lee (SteveL-MSFT) wants to merge 4 commits into
mainfrom
stevel-msft-ignore-instructed-schema-diffs

Conversation

@SteveL-MSFT

@SteveL-MSFT Steve Lee (SteveL-MSFT) commented Aug 13, 2026

Copy link
Copy Markdown
Member

Properties used only as resource instructions may not be returned by a resource, which caused schema-aware comparisons to report false differences.

This change makes get_diff_with_schema() skip properties marked writeOnly: true in the resource schema. The Windows Firewall resource now marks unspecifiedRulesAction as write-only, and unit and resource tests cover differing, omitted, and explicitly non-write-only properties.

Fix #1668

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces false-positive schema-aware differences when resources intentionally do not return “instruction-only” properties by making get_diff_with_schema() ignore schema properties marked writeOnly: true. It also updates the Windows Firewall resource schema and tests to reflect that unspecifiedRulesAction is write-only and should not affect desired-state comparisons.

Changes:

  • Update get_diff_with_schema() to skip properties whose JSON Schema sets writeOnly: true, and add unit tests for write-only behavior.
  • Mark unspecifiedRulesAction as writeOnly in the Windows Firewall resource schema.
  • Adjust Windows Firewall resource tests to assert non-default unspecifiedRulesAction values do not cause diffs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
resources/windows_firewall/windows_firewall.dsc.resource.json Marks unspecifiedRulesAction as writeOnly so it’s excluded from schema-aware diffing.
resources/windows_firewall/tests/windows_firewall_schema_default.tests.ps1 Updates resource tests to assert unspecifiedRulesAction does not affect inDesiredState/diff output.
lib/dsc-lib/src/dscresources/dscresource.rs Implements write-only skipping in schema-aware diffs and adds unit tests for the behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/dsc-lib/src/dscresources/dscresource.rs
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

😁 Code Coverage Report

Changed Code Coverage

94% (90%+ coverage)

Metric Value
Changed lines analyzed 74
Lines covered by tests 70
Coverage percentage 94%

🔵 Full Codebase Coverage

82% (good)

Metric Value
Total executable lines 18820
Lines covered by tests 15500
Coverage percentage 82%

Changed code coverage measures only Rust lines added/modified in this PR.
Full codebase coverage measures all instrumented Rust lines across the project.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

/// Returns whether a property's JSON Schema sets `writeOnly` to `true`, directly or
/// through a local JSON Pointer reference.
fn is_schema_write_only(schema: Option<&Value>, property_name: &str) -> bool {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I was going to recommend implementing this would be easier in the dsc-lib-jsonschema crate, but thinking about this a bit more, I think the ergonomics and simplicity will mostly improve when we implement the in-memory schema registry and retriever, when we can call deference() to get the full property definition for verification.

Comment on lines +795 to +797
let Some(pointer) = reference.strip_prefix('#') else {
return false;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for pointer references like #/$defs/foo but not URI references (site-relative or absolute), like https://schemas.contoso.com/foo or /foo.

The formalized bundling format for JSON Schema 2020-12 (known more correctly as a compound schema document) is to ensure that you include any external schemas in the $defs keyword with their $id keyword.

For example, the following non-bundled schema:

{
  "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "Must be a non-negative integer",
  "$ref": "#/$defs/nonNegativeInteger"
  "$defs": {
    "nonNegativeInteger": {
      "allOf": [
        { "$ref": "/schemas/mixins/integer" },
        { "$ref": "/schemas/mixins/non-negative" }
      ]
    }
  },
}

Bundles to the following compound schema document:

{
  "$id": "https://jsonschema.dev/schemas/examples/non-negative-integer-bundle",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "Must be a non-negative integer",
  "$ref": "#/$defs/nonNegativeInteger"
  "$defs": {
    "nonNegativeInteger": {
      "allOf": [
        { "$ref": "/schemas/mixins/integer" },
        { "$ref": "/schemas/mixins/non-negative" }
      ]
    },
    "https://jsonschema.dev/schemas/mixins/integer": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "$id": "https://jsonschema.dev/schemas/mixins/integer",
      "description": "Must be an integer",
      "type": "integer"
    },
    "https://jsonschema.dev/schemas/mixins/non-negative": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "$id": "https://jsonschema.dev/schemas/mixins/non-negative",
      "description": "Not allowed to be negative",
      "minimum": 0
    }
  },
}

When a validator resolves a relative URI like /schemas/mixins/integer, it does so _relative to the schemas $id URI.

The key in the $defs for bundled schema resources doesn't matter, using the absolute URI is a convention that ensures unique keys for each resource. What the resolver does is look for a subschema in $defs that defines an $id that matches the reference.

We have an extension method on the schemars::Schema type for checking whether a reference is to a bundled schema resource that handles both pointers and absolute/relative URI references:

/// Checks whether a given reference maps to a bundled schema resource.
///
/// This method takes the value of a `$ref` keyword and searches for a matching entry in the
/// `$defs` keyword. The method returns `true` if the reference resolves to an entry in
/// `$defs` and otherwise false.
///
/// The reference can be any of the following:
///
/// - A URI identifier fragment, like `#/$defs/foo`
/// - An absolute URL for the referenced schema, like `https://contoso.com/schemas/example.json`
/// - A site-relative URL for the referenced schema, like `/schemas/example.json`. The function
/// can only resolve site-relative URLs when the schema itself defines `$id` with an absolute
/// URL, because it uses the current schema's `$id` as the base URL.
///
/// # Examples
///
/// ```rust
/// use schemars::json_schema;
/// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions;
///
/// let schema = &json_schema!({
/// "$id": "https://contoso.com/schemas/example/object.json",
/// "$defs": {
/// "name": {
/// "$id": "https://contoso.com/schemas/example/properties/name.json",
/// "type": "string"
/// }
/// }
/// });
///
/// // Resolving reference as pointer
/// assert_eq!(schema.reference_is_for_bundled_resource("#/$defs/name"), true);
/// // Resolving reference as site-relative URI
/// assert_eq!(
/// schema.reference_is_for_bundled_resource("/schemas/example/properties/name.json"),
/// true
/// );
/// // Resolving reference as absolute URI
/// assert_eq!(
/// schema.reference_is_for_bundled_resource(
/// "https://contoso.com/schemas/example/properties/name.json"
/// ),
/// true
/// );
/// // Returns false for unresolvable definition
/// assert_eq!(schema.reference_is_for_bundled_resource("#/$defs/invalid"), false);
/// ```
fn reference_is_for_bundled_resource(&self, reference: &str) -> bool;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the limitations we have is we CAN'T do a HTTP call to retrieve URLs due to compliance restrictions

@SteveL-MSFT

Copy link
Copy Markdown
Member Author

Copilot resolve the merge conflicts in this pull request

Copilot AI and others added 2 commits August 14, 2026 20:26
Co-authored-by: SteveL-MSFT <11859881+SteveL-MSFT@users.noreply.github.com>
Co-authored-by: SteveL-MSFT <11859881+SteveL-MSFT@users.noreply.github.com>

Copilot AI commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved the conflicts by merging main in ed4531b6; e3f76e92 also ensures the replacement unspecifiedRules write-only instruction is not returned from set operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable JSONSchema property decorator to inform synthetic test to skip

4 participants