From 150bac8189775b807396f514a92b5d3b947404ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Sat, 19 Sep 2026 13:30:15 +0900 Subject: [PATCH] feat(policy)!: remove warning acknowledgement gate Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- Cargo.lock | 4 ++-- policies/dotnet/Devolutions.Now.Policy.Api/Enums.cs | 1 - .../PolicyManagementModels.cs | 4 ---- policies/dotnet/Devolutions.Now.Policy.Api/README.md | 3 +++ .../PolicyManagementClientTests.cs | 11 +++++++++++ policies/rust/now-policy-api/Cargo.toml | 2 +- policies/rust/now-policy-api/README.md | 3 +++ .../rust/now-policy-api/openapi/now-policy-api.yaml | 5 ----- policies/rust/now-policy-api/src/enums.rs | 1 - policies/rust/now-policy-api/src/management.rs | 3 --- policies/rust/now-policy-server-template/Cargo.toml | 4 ++-- .../rust/now-policy-server-template/src/server.rs | 5 +---- .../tests/sample_documents.rs | 11 +++++++++-- .../requests/policy-replacement.create.request.json | 1 - .../policy-replacement.overwrite.request.json | 1 - .../requests/policy-replacement.repair.request.json | 1 - .../policy-replacement.replace-identity.request.json | 1 - .../requests/policy-replacement.update.request.json | 1 - 18 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 228c722..dda8f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,7 +626,7 @@ dependencies = [ [[package]] name = "now-policy-api" -version = "0.6.0" +version = "0.7.0" dependencies = [ "chrono", "derive_more", @@ -641,7 +641,7 @@ dependencies = [ [[package]] name = "now-policy-server-template" -version = "0.6.0" +version = "0.7.0" dependencies = [ "aide", "async-trait", diff --git a/policies/dotnet/Devolutions.Now.Policy.Api/Enums.cs b/policies/dotnet/Devolutions.Now.Policy.Api/Enums.cs index 5e5525f..6eb29ef 100644 --- a/policies/dotnet/Devolutions.Now.Policy.Api/Enums.cs +++ b/policies/dotnet/Devolutions.Now.Policy.Api/Enums.cs @@ -150,7 +150,6 @@ public enum ErrorCode UnsupportedEndpoint, MalformedDraft, InvalidPolicy, - WarningConfirmationRequired, Unauthenticated, AdministratorRequired, UnsafePolicyPath, diff --git a/policies/dotnet/Devolutions.Now.Policy.Api/PolicyManagementModels.cs b/policies/dotnet/Devolutions.Now.Policy.Api/PolicyManagementModels.cs index 61626b1..c1e57b0 100644 --- a/policies/dotnet/Devolutions.Now.Policy.Api/PolicyManagementModels.cs +++ b/policies/dotnet/Devolutions.Now.Policy.Api/PolicyManagementModels.cs @@ -359,10 +359,6 @@ public string RequestKind [JsonRequired] public PolicyConflictHandling ConflictHandling { get; set; } - [JsonPropertyName("WarningsAcknowledged")] - [JsonRequired] - public bool WarningsAcknowledged { get; set; } - /// Raw draft JSON retained for transaction-time reparsing and revalidation. [JsonPropertyName("Draft")] [JsonRequired] diff --git a/policies/dotnet/Devolutions.Now.Policy.Api/README.md b/policies/dotnet/Devolutions.Now.Policy.Api/README.md index 4ba702d..135dd3b 100644 --- a/policies/dotnet/Devolutions.Now.Policy.Api/README.md +++ b/policies/dotnet/Devolutions.Now.Policy.Api/README.md @@ -42,6 +42,9 @@ Opaque policy store tokens and validation receipts are restricted to safe printa HTTP body of policy validation and replacement requests. It is separate from the package-operation limit advertised by broker capabilities. +Policy replacement retains the validation receipt, expected store token, operation, and explicit overwrite-conflict handling. +Validation warnings are advisory findings for inline client UX and do not require a wire-level acknowledgement to save a valid draft. + Because policy documents are JSON-only, configured `.yaml`, `.yml`, extensionless, and other non-JSON paths use `PolicyReadOnlyReason.UnsupportedFormat` in management snapshots and `ErrorCode.UnsupportedPolicyFormat` for structured HTTP 422 errors. diff --git a/policies/dotnet/Devolutions.Now.Policy.Client.Tests/PolicyManagementClientTests.cs b/policies/dotnet/Devolutions.Now.Policy.Client.Tests/PolicyManagementClientTests.cs index fdf2e06..92d721c 100644 --- a/policies/dotnet/Devolutions.Now.Policy.Client.Tests/PolicyManagementClientTests.cs +++ b/policies/dotnet/Devolutions.Now.Policy.Client.Tests/PolicyManagementClientTests.cs @@ -82,6 +82,17 @@ public async Task ReplacePolicy_sends_every_operation_intent( Assert.Equal("store:active:8", response.Management.StoreToken); } + [Fact] + public async Task PolicyReplacement_rejects_legacy_warning_acknowledgement() + { + var request = JsonNode.Parse(await ReadFixture("requests", "policy-replacement.update.request.json"))!; + request["WarningsAcknowledged"] = true; + + var json = request.ToJsonString(); + Assert.Throws(() => BrokerSerializer.DeserializeStrict(json)); + Assert.NotEmpty((await TestData.SchemaAsync("PolicyReplacementRequest")).Validate(json)); + } + [Fact] public async Task ReplacePolicy_preserves_structured_stale_token_findings() { diff --git a/policies/rust/now-policy-api/Cargo.toml b/policies/rust/now-policy-api/Cargo.toml index 23b06bc..2e7034f 100644 --- a/policies/rust/now-policy-api/Cargo.toml +++ b/policies/rust/now-policy-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "now-policy-api" -version = "0.6.0" +version = "0.7.0" edition = "2024" license.workspace = true homepage.workspace = true diff --git a/policies/rust/now-policy-api/README.md b/policies/rust/now-policy-api/README.md index 66734f9..dd69782 100644 --- a/policies/rust/now-policy-api/README.md +++ b/policies/rust/now-policy-api/README.md @@ -61,6 +61,9 @@ The generated document contains the unchanged policy inspection route, the manag Opaque store tokens and validation receipts use safe printable ASCII (`A-Z`, `a-z`, `0-9`, `.`, `_`, `~`, `:`, `-`) and begin with an ASCII alphanumeric character. This keeps length and validation behavior identical across Rust UTF-8 and .NET UTF-16 implementations. +Replacement requests retain their validation receipt, expected store token, operation, and explicit overwrite-conflict handling. +Validation warnings are advisory findings that clients present inline and never require a protocol-level acknowledgement to save a valid draft. + Validation ---------- diff --git a/policies/rust/now-policy-api/openapi/now-policy-api.yaml b/policies/rust/now-policy-api/openapi/now-policy-api.yaml index 74d8cef..217c3e5 100644 --- a/policies/rust/now-policy-api/openapi/now-policy-api.yaml +++ b/policies/rust/now-policy-api/openapi/now-policy-api.yaml @@ -602,7 +602,6 @@ components: - UnsupportedEndpoint - MalformedDraft - InvalidPolicy - - WarningConfirmationRequired - Unauthenticated - AdministratorRequired - UnsafePolicyPath @@ -1419,9 +1418,6 @@ components: $ref: '#/components/schemas/ApiVersion' ValidationReceipt: $ref: '#/components/schemas/PolicyValidationReceipt' - WarningsAcknowledged: - description: Explicit acknowledgement of every warning bound into the validation receipt. - type: boolean additionalProperties: false required: - RequestKind @@ -1429,7 +1425,6 @@ components: - ExpectedStoreToken - Operation - ConflictHandling - - WarningsAcknowledged - Draft - ValidationReceipt PolicyReplacementRequestKind: diff --git a/policies/rust/now-policy-api/src/enums.rs b/policies/rust/now-policy-api/src/enums.rs index ea41109..170c630 100644 --- a/policies/rust/now-policy-api/src/enums.rs +++ b/policies/rust/now-policy-api/src/enums.rs @@ -128,7 +128,6 @@ pub enum ErrorCode { UnsupportedEndpoint, MalformedDraft, InvalidPolicy, - WarningConfirmationRequired, Unauthenticated, AdministratorRequired, UnsafePolicyPath, diff --git a/policies/rust/now-policy-api/src/management.rs b/policies/rust/now-policy-api/src/management.rs index 6311d3c..fa17468 100644 --- a/policies/rust/now-policy-api/src/management.rs +++ b/policies/rust/now-policy-api/src/management.rs @@ -698,9 +698,6 @@ pub struct PolicyReplacementRequest { pub operation: PolicyReplacementOperation, pub conflict_handling: PolicyConflictHandling, - /// Explicit acknowledgement of every warning bound into the validation receipt. - pub warnings_acknowledged: bool, - /// Raw draft JSON retained for transaction-time reparsing and revalidation. pub draft: serde_json::Value, diff --git a/policies/rust/now-policy-server-template/Cargo.toml b/policies/rust/now-policy-server-template/Cargo.toml index d7f754b..e8383df 100644 --- a/policies/rust/now-policy-server-template/Cargo.toml +++ b/policies/rust/now-policy-server-template/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "now-policy-server-template" -version = "0.6.0" +version = "0.7.0" edition = "2024" license.workspace = true homepage.workspace = true @@ -17,7 +17,7 @@ workspace = true aide = { version = "0.15", features = ["axum", "axum-json"] } async-trait = "0.1" axum = { version = "0.8", default-features = false, features = ["json"] } -now-policy-api = { version = "0.6", path = "../now-policy-api" } +now-policy-api = { version = "0.7", path = "../now-policy-api" } now-policy = { version = "0.5", path = "../now-policy" } schemars = "0.9" serde = { version = "1", features = ["derive"] } diff --git a/policies/rust/now-policy-server-template/src/server.rs b/policies/rust/now-policy-server-template/src/server.rs index e8d9f0d..59cd92c 100644 --- a/policies/rust/now-policy-server-template/src/server.rs +++ b/policies/rust/now-policy-server-template/src/server.rs @@ -406,10 +406,7 @@ fn error_status(code: ErrorCode) -> StatusCode { ErrorCode::Unauthorized | ErrorCode::Unauthenticated => StatusCode::UNAUTHORIZED, ErrorCode::Forbidden | ErrorCode::AdministratorRequired => StatusCode::FORBIDDEN, ErrorCode::NotFound => StatusCode::NOT_FOUND, - ErrorCode::Conflict - | ErrorCode::WarningConfirmationRequired - | ErrorCode::UnsafePolicyPath - | ErrorCode::StalePolicyStoreToken => StatusCode::CONFLICT, + ErrorCode::Conflict | ErrorCode::UnsafePolicyPath | ErrorCode::StalePolicyStoreToken => StatusCode::CONFLICT, ErrorCode::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE, ErrorCode::UnsupportedMediaType => StatusCode::UNSUPPORTED_MEDIA_TYPE, ErrorCode::ValidationFailed diff --git a/policies/rust/now-policy-server-template/tests/sample_documents.rs b/policies/rust/now-policy-server-template/tests/sample_documents.rs index d685834..a4b5404 100644 --- a/policies/rust/now-policy-server-template/tests/sample_documents.rs +++ b/policies/rust/now-policy-server-template/tests/sample_documents.rs @@ -136,6 +136,14 @@ fn all_sample_requests_deserialize() { } } +#[test] +fn policy_replacement_rejects_legacy_warning_acknowledgement() { + let mut request = load_json_file(&samples_dir().join("requests/policy-replacement.update.request.json")); + request["WarningsAcknowledged"] = true.into(); + + assert!(serde_json::from_value::(request).is_err()); +} + #[test] fn all_sample_responses_deserialize() { for path in json_files(&samples_dir().join("responses")) { @@ -587,7 +595,6 @@ async fn policy_management_error_codes_use_stable_http_statuses() { (ErrorCode::UnsupportedEndpoint, StatusCode::NOT_IMPLEMENTED), (ErrorCode::MalformedDraft, StatusCode::BAD_REQUEST), (ErrorCode::InvalidPolicy, StatusCode::UNPROCESSABLE_ENTITY), - (ErrorCode::WarningConfirmationRequired, StatusCode::CONFLICT), (ErrorCode::Unauthenticated, StatusCode::UNAUTHORIZED), (ErrorCode::AdministratorRequired, StatusCode::FORBIDDEN), (ErrorCode::UnsafePolicyPath, StatusCode::CONFLICT), @@ -745,7 +752,7 @@ async fn policy_management_routes_accept_exact_limit_and_reject_one_byte_over() ( "PUT", "/v1/policy", - r#"{"RequestKind":"PolicyReplacementRequest","RequestVersion":"1.0","ExpectedStoreToken":"store:active:7","Operation":"Update","ConflictHandling":"Reject","WarningsAcknowledged":true,"Draft":{"Padding":""#, + r#"{"RequestKind":"PolicyReplacementRequest","RequestVersion":"1.0","ExpectedStoreToken":"store:active:7","Operation":"Update","ConflictHandling":"Reject","Draft":{"Padding":""#, r#""},"ValidationReceipt":"receipt:sha256:test"}"#, ), ]; diff --git a/policies/test-data/package-broker/requests/policy-replacement.create.request.json b/policies/test-data/package-broker/requests/policy-replacement.create.request.json index d1b59d8..f90ce5e 100644 --- a/policies/test-data/package-broker/requests/policy-replacement.create.request.json +++ b/policies/test-data/package-broker/requests/policy-replacement.create.request.json @@ -4,7 +4,6 @@ "ExpectedStoreToken": "store:missing:0", "Operation": "Create", "ConflictHandling": "Reject", - "WarningsAcknowledged": false, "Draft": { "PolicyFormatVersion": "1.0.0", "Metadata": { "Id": "contoso.package-policy", "Publisher": "Contoso IT" }, diff --git a/policies/test-data/package-broker/requests/policy-replacement.overwrite.request.json b/policies/test-data/package-broker/requests/policy-replacement.overwrite.request.json index 2ee71f9..f932559 100644 --- a/policies/test-data/package-broker/requests/policy-replacement.overwrite.request.json +++ b/policies/test-data/package-broker/requests/policy-replacement.overwrite.request.json @@ -4,7 +4,6 @@ "ExpectedStoreToken": "store:active:newly-observed-8", "Operation": "Update", "ConflictHandling": "ConfirmOverwrite", - "WarningsAcknowledged": true, "Draft": { "PolicyFormatVersion": "1.0.0", "Metadata": { "Id": "contoso.package-policy", "Publisher": "Contoso IT" }, diff --git a/policies/test-data/package-broker/requests/policy-replacement.repair.request.json b/policies/test-data/package-broker/requests/policy-replacement.repair.request.json index bc5c6bd..4df8202 100644 --- a/policies/test-data/package-broker/requests/policy-replacement.repair.request.json +++ b/policies/test-data/package-broker/requests/policy-replacement.repair.request.json @@ -4,7 +4,6 @@ "ExpectedStoreToken": "store:invalid:4", "Operation": "Repair", "ConflictHandling": "Reject", - "WarningsAcknowledged": false, "Draft": { "PolicyFormatVersion": "1.0.0", "Metadata": { "Id": "contoso.package-policy", "Publisher": "Contoso IT" }, diff --git a/policies/test-data/package-broker/requests/policy-replacement.replace-identity.request.json b/policies/test-data/package-broker/requests/policy-replacement.replace-identity.request.json index a3e636e..721606e 100644 --- a/policies/test-data/package-broker/requests/policy-replacement.replace-identity.request.json +++ b/policies/test-data/package-broker/requests/policy-replacement.replace-identity.request.json @@ -4,7 +4,6 @@ "ExpectedStoreToken": "store:active:7", "Operation": "ReplaceIdentity", "ConflictHandling": "Reject", - "WarningsAcknowledged": false, "Draft": { "PolicyFormatVersion": "1.0.0", "Metadata": { "Id": "fabrikam.package-policy", "Publisher": "Fabrikam IT" }, diff --git a/policies/test-data/package-broker/requests/policy-replacement.update.request.json b/policies/test-data/package-broker/requests/policy-replacement.update.request.json index 42f4310..4febb09 100644 --- a/policies/test-data/package-broker/requests/policy-replacement.update.request.json +++ b/policies/test-data/package-broker/requests/policy-replacement.update.request.json @@ -4,7 +4,6 @@ "ExpectedStoreToken": "store:active:7", "Operation": "Update", "ConflictHandling": "Reject", - "WarningsAcknowledged": true, "Draft": { "PolicyFormatVersion": "1.0.0", "Metadata": { "Id": "contoso.package-policy", "Publisher": "Contoso IT" },