Skip to content

Add Policy ARM resource for CognitiveServices (2026-05-15-preview)#43974

Merged
fmabroukmsft merged 11 commits into
Azure:swagger-agent/CogSvc-2026-05-15-preview-typespecfrom
djetchev:djetchev/add-policy-2026-05-15-preview
Jun 23, 2026
Merged

Add Policy ARM resource for CognitiveServices (2026-05-15-preview)#43974
fmabroukmsft merged 11 commits into
Azure:swagger-agent/CogSvc-2026-05-15-preview-typespecfrom
djetchev:djetchev/add-policy-2026-05-15-preview

Conversation

@djetchev

Copy link
Copy Markdown

Summary

Add account-level Policy ARM resource to CognitiveServices for controlling sandbox egress traffic. This PR targets the existing 2026-05-15-preview API version branch per RP team guidance from Fareed.

Changes

New files

  • Policy.tsp — PolicyResource model + CRUD operations (get, createOrUpdate, delete, list)
  • examples/2026-05-15-preview/AccountPolicy/ — 4 example JSON files

Modified files

  • main.tsp — Added Policy.tsp import
  • models.tsp — Added Policy unions and models:
    • PolicyType (Egress)
    • PolicyMode (Enforced, Audit)
    • PolicyDefaultAction (Allow, Deny)
    • PolicyRuleAction (Allow, Deny, Transform, Rewrite)
    • PolicyRuleType (Fqdn)
    • PolicyRuleMatch, PolicyRule, PolicyProperties, PolicyListResult

Design decisions

  • Account-scoped only — Aligns with RAI policies and networking (no project-level)
  • Extensible unions — All enums use union pattern for future extensibility (e.g., MCP rule type)
  • Audit mode — Supports enforcement mode toggle per Evan Mattson's implementation
  • Transform/Rewrite actions — Included per Evan's request for header injection support
  • No methods field — Removed per Evan's feedback (easier to add later if needed)

Related PRs

Add account-level Policy resource for controlling sandbox egress traffic.
Targets existing 2026-05-15-preview API version per RP team guidance.

- Policy.tsp: PolicyResource + CRUD operations (get, createOrUpdate, delete, list)
- models.tsp: PolicyType, PolicyMode, PolicyDefaultAction, PolicyRuleAction,
  PolicyRuleType, PolicyRuleMatch, PolicyRule, PolicyProperties, PolicyListResult
- 4 example files for account-level Policy operations
- Generated swagger updated

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

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Next Steps to Merge

✅ All automated merging requirements have been met! To get your PR merged, see aka.ms/azsdk/specreview/merge.

Comment generated by summarize-checks workflow run.

@github-actions github-actions Bot added resource-manager TypeSpec Authored with TypeSpec labels Jun 15, 2026
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

API Change Check

APIView identified API level changes in this PR and created the following API reviews

Language API Review for Package
TypeSpec Microsoft.CognitiveServices
Go sdk/resourcemanager/cognitiveservices/armcognitiveservices
JavaScript @azure/arm-cognitiveservices
Java com.azure.resourcemanager:azure-resourcemanager-cognitiveservices
Python azure-mgmt-cognitiveservices

Comment generated by After APIView workflow run.

@fmabroukmsft

Copy link
Copy Markdown
Member

Model-design questions before this ships in preview:

1. Policy* naming collides with existing types. The base swagger already exposes PolicyAssignmentEvaluationDetails, PolicyEvaluationOutcome, and PolicyExpressionEvaluationDetails from EvaluateDeploymentPolicies. Generic Policy* overlaps in SDK auto-complete and docs. SandboxPolicy* or FoundryPolicy* would preserve the future-proofing for ingress without the overload.

2. PolicyRuleAction = Transform | Rewrite doesn't apply to PolicyRuleType = Fqdn. FQDN/DNS-level matching can't transform headers or rewrite paths. Either document that Transform/Rewrite are reserved for future rule types and have the server 400 them when combined with ruleType=Fqdn, or defer adding them until a rule type uses them. Commit 14a0d79 dropped them per Xi's guidance before f47610d re-added them per Evan's — confirm alignment.

3. PolicyMode default is doc-only. The doc says "Defaults to Enforced" but the schema has no default. Pin the server contract: on GET when the client omitted mode on PUT, does the response echo "Enforced" or null?

4. defaultAction is optional and security-critical. If unset and no rule matches, the fallback (Allow vs. Deny) materially changes the policy's behavior. Either make defaultAction required, or document the server-applied default explicitly.

5. PolicyRuleMatch is all-optional and glob semantics are unspecified. PolicyRuleMatch {} matches everything by absence — state this. For host: "*.openai.com" and path: "/v1/*", pin the syntax (DNS wildcard for host; POSIX glob, URI glob with **, or something else for path).

6. Name pattern {2,32} caps at 33 chars. Short for an ARM child resource (typical 63). If a backend storage constraint drives this, note it; otherwise consider {2,62}.

7. Unused import "./Project.tsp" in Policy.tsp. Leftover from the project-scoped operations removed in afbd31c.

Items 3, 4, and 5 are contract-level — preferable to resolve before the preview goes out. 1, 2, 6 are renames/scope decisions that get more expensive after release. 7 is a cleanup.

djetchev and others added 2 commits June 16, 2026 15:10
…ules

- Rename PolicyResource → NetworkPolicyResource (avoids collision with existing Policy* types)
- Rename AccountPolicies → AccountNetworkPolicies interface
- Change segment from /policies to /networkPolicies
- Add discriminated NetworkPolicyRuleAction with Allow, Deny, Transform, Rewrite action types
- Transform: header operations (Add/Remove/Overwrite), extensible
- Rewrite: host + port rewriting, extensible
- Add read-only systemRules for server-populated infrastructure allowlist
- Make defaultAction required (security-critical)
- Document match semantics (DNS wildcard for host, URI prefix with * for path)
- Expand name pattern from {2,32} to {2,62}
- Remove unused Project.tsp import
- Address all 7 review comments from Fareed on PR Azure#43974

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

- Rename all NetworkPolicy* types, models, interfaces, and examples to AgentGuardrails*
- Flatten action payload: headers[] and rewrite directly on AgentGuardrailsRuleAction
  (removed transformConfig/rewriteConfig wrappers)
- Add secret reference models: AgentGuardrailsSecretRef, AgentGuardrailsManagedIdentityRef,
  AgentGuardrailsHeaderValueRef, AgentGuardrailsManagedIdentityType
- Make header value write-only (@secret + @visibility(Create, Update)) to prevent
  credential leakage on GET
- Rename header operations: Add->Insert, Overwrite->Set for clarity
- Enrich rewrite target: add scheme, path, keep targetPort
- Fix Audit mode comment: dry-run for Deny only; Transform/Rewrite still execute
- Add Transform (secretRef) and Rewrite (managedIdentityRef) JSON examples

Based on Evan's feedback on PR Azure#43974.

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

Copy link
Copy Markdown
Member

Typespec validation is failing, can be fixed by adding @secret to secretKey in AgentGuardrailsSecretRef

@fmabroukmsft

Copy link
Copy Markdown
Member

There are orphaned example files from the renames, those will need to be deleted

@fmabroukmsft

Copy link
Copy Markdown
Member

Swagger will need recompilation with tsx compile

- Add @secret to AgentGuardrailsSecretRef.secretKey (fixes TypeSpec validation)
- Delete orphaned AccountPolicy/ and AccountNetworkPolicy/ example files
- Recompile swagger with AgentGuardrails naming

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

Copy link
Copy Markdown
Author

All addressed in commit b2a9423:

1. Policy* naming collision — Renamed to AgentGuardrails* throughout (resource, operations, models, examples). No collision with existing types.

2. Transform/Rewrite on FQDN — These action types are retained per Evan/Chetan's guidance for future rule types. Server will return 400 when combined with ruleType=Fqdn. Documented in model comments.

3. AgentGuardrailsMode default — Server echoes "Enforced" on GET when client omits mode on PUT. Doc updated to reflect this.

4. defaultAction required — Now required in the schema (no longer optional).

5. Match semantics documentedhost: DNS wildcard (*.openai.com). path: URI prefix with trailing * glob. Empty match = match-all, documented.

6. Name pattern — Expanded from {2,32} to {2,62}.

7. Unused Project.tsp import — Removed.

Additionally:

  • Added @secret to AgentGuardrailsSecretRef.secretKey (fixes TypeSpec validation)
  • Deleted orphaned AccountPolicy/ and AccountNetworkPolicy/ example directories
  • Swagger recompiled with tsp compile

@djetchev

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Microsoft"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should this file be named AgentGuardrails instead?

* The action to take when this rule matches, including the action type and any
* type-specific configuration (headers for Transform, rewrite target for Rewrite).
*/
action?: AgentGuardrailsRuleAction;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

agentguardrailsRuleAction is optional, what happens when no action is defined?

}

/**
* Where a Rewrite action sends matched traffic. At least one field must be set.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this comment says at least one field must be set but all the files in agentguardrailsrewritetarget are optional

Per RAI team decision (PR Platform-ResourceProvider#16147771), egress
guardrails are no longer a standalone ARM resource. Instead, they are a
nested egressPolicy property on the existing RaiPolicyProperties model,
following the same extension pattern as contentFilters and customBlocklists.

Changes:
- Remove Policy.tsp (standalone AgentGuardrailsResource and CRUD ops)
- Remove import from main.tsp
- Replace all AgentGuardrails* types with RaiEgress* types in models.tsp
- Add egressPolicy?: RaiEgressPolicyConfig to RaiPolicyProperties
- Add PutRaiPolicyWithEgress.json example showing egress within RaiPolicy
- Remove all AccountAgentGuardrails example files (source + generated)
- Recompile swagger

The RaiEgress* types align 1:1 with the backend C# contract (RaiEgressPolicyConfig,
RaiEgressRule, RaiEgressRuleAction, etc.) from the RP team's PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
"host": "*.internal.contoso.com"
},
"action": {
"actionType": "Transform",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we add an example with rewrite action type too.

@fmabroukmsft

Copy link
Copy Markdown
Member

Reviewed the swagger against the backend service PR (Platform-ResourceProvider #16147771, branch liamcrumm/rai-egress-policy): RaiEgressPolicyConfig.cs, RaiPolicyValidator.cs, InternalRaiEgressProjection.cs, RaiPolicyProjectionProvider.cs, and RaiPolicyProperties.cs. The change is additive (+15 definitions, no path or definition removals), egressPolicy is scoped to the 2026-05-15-preview swagger only, and CI is green (LintDiff, ModelValidation, SemanticValidation, BreakingChange, Avocado, Prettier, APIView, ARM Auto SignOff, all 6 SDK validations). Field-by-field the required/optional flags, the discriminated action shape, enum value sets, and write-only handling of HeaderTransform.value align with the backend, and the validator enforces every documented multi-field constraint server-side.

Contract divergences (swagger vs. backend)

1. systemRules exists in the swagger but not in the backend.
RaiEgressPolicyConfig declares a read-only systemRules array, and PutRaiPolicyWithEgress.json shows it populated in the 200/201 responses. The backend has no systemRules: absent from RaiEgressPolicyConfig.cs (only Mode, DefaultAction, Description, Rules), absent from InternalRaiEgressProjection.cs (carries only mode, defaultAction, rules), and unreferenced across the RAI contracts. The service will not return systemRules, so the example response is inaccurate and SDK ModelValidation/round-trip will assert a field the service does not emit. Resolution: remove systemRules from the spec and example, or add it to the backend contract + projection + redaction clone if it is intended for this release.

2. secretKey is marked x-ms-secret in the swagger but not redacted by the backend.
RaiEgressSecretRef.secretKey is x-ms-secret: true (and, unlike value, has no x-ms-mutability, so it is declared secret yet returned on read). RedactSecretsForResponse() nulls only header.Value, not secretKey, so the service returns secretKey on GET. secretKey is documented as "Optional key within the secret" — a lookup key, not the credential — which suggests it is not actually secret. Resolution: drop @secret from secretKey if it is a key name (most likely), or make it write-only and redact it server-side if it is sensitive.

ARM modeling best practices (lint passes, but worth addressing)

3. identityResourceId should use arm-id format. It is a user-assigned managed identity resource ID but is modeled as a plain string. Mark it with @armResourceIdentifier (→ format: arm-id) so ARM can validate the cross-resource reference. (ManagedIdentityRef.resource is a token audience URI, not an ARM ID, and is correctly left as a plain string.)

Example coverage

4. PutRaiPolicyWithEgress.json exercises only Allow + Transform actions, the Set header op, and managedIdentityRef. It does not cover the Rewrite action (with a rewrite target), Deny, Insert/Remove ops, secretRef, the static write-only value (which would document the redaction round-trip), or Audit mode. A second example covering these would improve documentation and SDK round-trip coverage for this security-sensitive feature. Optional.

Item 1 is the main item to resolve before merge; item 2 is a small spec/code reconciliation. The rest are non-blocking improvements.

}
}
],
"systemRules": [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Does this need to go to the user ? I was under the impression that Agent Service will be injecting these and these won't be in the ARM resource. Or are we adding it in the ARM resource.


/**
* Rules are evaluated and logged but not enforced. A would-be Deny is logged but traffic
* is still forwarded, allowing customers to preview policy impact without breaking traffic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we add that transform and rewrite will still be performed too. Basically this applies only to deny.

- Audit mode doc: clarify Transform/Rewrite still apply, only Deny is suppressed
- RewriteTarget doc: clarify server-side 400 validation for empty target
- Example: add Rewrite rule with scheme/host/path/targetPort + header
- Example: rewrite rule appears in request, 200, and 201 response bodies

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

Copy link
Copy Markdown
Member

The RP redacts the write-only header value on read (RedactSecretsForResponse), so the value echoed back in the 200/201 example responses wouldn't actually be returned by the server. Drop it from the response bodies so the example matches live behavior.

Per agreement with Chetan: remove the user/system rules distinction.
All rules live in one ordered list. System rules can be added later
if needed (easier to add than rollback).

- Remove systemRules property from RaiEgressPolicyConfig model
- Remove systemRules from example response bodies
- Recompile swagger

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove RaiEgressManagedIdentityType union, ManagedIdentityRef.type,
ManagedIdentityRef.identityResourceId, and RewriteTarget.targetPort.

These fields cannot be enforced end-to-end today because the ADC client
SDK (v26.61.0) does not expose Type/IdentityResourceId on
EgressPolicyManagedIdentityRef, and has no Port on EgressPolicyRuleAction.

The system-assigned managed identity is used implicitly when type is
omitted. Fields will be re-added when ADC ships SDK support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
"scheme": "https",
"host": "api-v2.internal.contoso.com",
"path": "/v2/{path}",
"targetPort": 8443

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Port needs to go out

"managedIdentityRef": {
"resource": "https://internal.contoso.com/.default",
"format": "Bearer {value}",
"type": "SystemAssigned"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Type needs to go out too.

Re-ran tsp compile to update cognitiveservices.json and the
PutRaiPolicyWithEgress.json example. Removes type/targetPort
that were manually edited but left stale in the generated output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
"managedIdentityRef": {
"resource": "https://internal.contoso.com/.default",
"format": "Bearer {value}",
"type": "SystemAssigned"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The generated resource-manager artifacts look out of sync with the source. This example still has type here, and the swagger schema (cognitiveservices.json) still defines type and identityResourceId on RaiEgressManagedIdentityRef, even though models.tsp and the source example dropped them. Looks like tsp wasn't recompiled after the model change. Re-running the generator should bring the swagger and generated examples back in line.

"scheme": "https",
"host": "api-v2.internal.contoso.com",
"path": "/v2/{path}",
"targetPort": 8443

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same regeneration gap as the managed-identity type note: targetPort is still here and in the swagger's RaiEgressRewriteTarget, though models.tsp and the source example removed it. A tsp recompile should clear both.

{
"operation": "Set",
"name": "X-Forwarded-Host",
"value": "legacy-api.contoso.com"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

value is write-only in models.tsp (@visibility(Lifecycle.Create, Lifecycle.Update)), so it should not appear in a response body. It is in the 200 response here and the 201 response (around line 223). Recommend dropping value from the response bodies and keeping it only in the request.

"rewrite": {
"scheme": "https",
"host": "api-v2.internal.contoso.com",
"path": "/v2/{path}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

/v2/{path} uses a {path} placeholder that isn't described in the RaiEgressRewriteTarget.path doc (which only says the original path is kept when omitted). Could we document the templating tokens, or drop the placeholder from the example? Also worth confirming ADC supports path templating on rewrite.

@vicondoa

Copy link
Copy Markdown

Small thing on the PR description: the title and summary describe a standalone Policy.tsp ARM resource (PolicyType/PolicyMode/PolicyRule, "Supersedes #43898"), but the current diff nests egressPolicy into RaiPolicyProperties and adds the RaiEgress* models. Could we update the description so reviewers and the changelog match what is shipping?

djetchev and others added 2 commits June 23, 2026 22:19
…ict scheme

1. ruleType: made required (only Fqdn supported today)
2. defaultAction: made optional, server defaults to Deny (fail-closed)
3. scheme: restricted to RaiEgressScheme union (http/https only)

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

1. Remove 'value' from response bodies (write-only per @visibility)
2. Replace '/v2/{path}' with '/v2/' — no path templating support
3. Clarify defaultAction doc: deny only affects unmatched traffic;
   transform/rewrite rules are always applied to matched traffic
4. Regenerated swagger and examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@fmabroukmsft fmabroukmsft merged commit c1c5f28 into Azure:swagger-agent/CogSvc-2026-05-15-preview-typespec Jun 23, 2026
40 checks passed
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.

4 participants