Skip to content

feat: warn on empty policy startup - #3441

Closed
Sakuna Harinda (sakunaharinda) wants to merge 4 commits into
microsoft:mainfrom
sakunaharinda:feat/startup-validation-empty-policies
Closed

Sakuna Harinda (sakunaharinda) wants to merge 4 commits into
microsoft:mainfrom
sakunaharinda:feat/startup-validation-empty-policies

Conversation

@sakunaharinda

Copy link
Copy Markdown
Contributor

Description

This PR adds startup validation for the AgentMesh policy loading path so that an empty policy directory produces an explicit warning instead of silently proceeding with permissive governance behavior.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Maintenance (dependency updates, CI/CD, refactoring)
  • Security fix

Package(s) Affected

  • agent-os-kernel
  • agent-mesh
  • agent-runtime
  • agent-sre
  • agent-governance
  • docs / root

Checklist

  • My code follows the project style guidelines (ruff check)
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass (pytest)
  • I have updated documentation as needed
  • I have signed the Microsoft CLA

Attribution & Prior Art

  • This contribution does not contain code copied or derived from other projects without attribution
  • Any external projects that inspired this design are credited in code comments or documentation
  • If this PR implements functionality similar to an existing open-source project, I have listed it below

Prior art / related projects (if any):
N/A

AI Assistance

  • I can explain every meaningful change in this PR: what it does, why, and what tradeoffs were considered
  • I have run tests and verification appropriate for this change
  • No part of this PR was autonomously submitted by an AI agent without my review
  • I have not used AI to generate review comments on others' PRs

If AI tools materially shaped this change, briefly note what was used:
N/A

IP, Patents, and Licensing

  • This contribution does not implement patent-pending or patent-encumbered techniques
  • This contribution does not require an NDA or licensing agreement to understand or use
  • Any AI tools used have terms compatible with the MIT License

Related Issues

N/A

Signed-off-by: sakunaharinda <sakunaj1996@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 23:33

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions github-actions Bot added tests agent-mesh agent-mesh package labels Jul 26, 2026
@github-actions

Copy link
Copy Markdown

Welcome to the Agent Governance Toolkit! Thanks for your first pull request.
Please ensure tests pass, code follows style (ruff check), and you have signed the CLA.
See our Contributing Guide.

@github-actions github-actions Bot added the size/M Medium PR (< 200 lines) label Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `agentmesh/server/policy_server.py`

AI-generated review output. Treat it as untrusted analysis and verify before acting.

agentmesh/server/policy_server.py

  • test_reload_missing_policy_dir_preserves_loaded_state -- Add a test for behavior when the policy directory exists but is empty.
  • test_list_policies_reports_startup_warning_when_empty -- Add a test for multiple warnings in load_warnings when multiple issues are detected.

agentmesh/server/sidecar.py

  • test_ready_reports_startup_warning_when_no_policies -- Add a test for behavior when startup warnings are empty but policies are also not loaded.

@github-actions

github-actions Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

No security issues found.

@github-actions

github-actions Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

AI-generated review output. Treat it as untrusted analysis and verify before acting.

Docs Sync

  • def _validate_load_warnings() in agentmesh/server/policy_server.py -- missing docstring
  • def _validate_startup() in agentmesh/server/sidecar.py -- missing docstring
  • README.md -- no updates detected for the new startup warning behavior
  • CHANGELOG.md -- missing entry for the new startup validation behavior

@github-actions

github-actions Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

TL;DR: 0 blockers, 1 warning. Change improves startup validation but has a minor test coverage gap.

# Sev Issue Where
1 Warn No test for _validate_startup directly agentmesh/server/sidecar.py

Action items: Add a direct test for _validate_startup to ensure its behavior is independently verified.

Warnings: fine as follow-up PRs.

@github-actions

github-actions Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

AI-generated review output. Treat it as untrusted analysis and verify before acting.

API Compatibility

Severity Change Impact
High Added load_warnings field to the list_policies and reload_policies API responses in policy_server.py. Existing clients consuming these APIs may not expect the new field, which could cause issues if they are not designed to handle it.
High Added startup_warnings field to the ready and reload_policies API responses in sidecar.py. Existing clients consuming these APIs may not expect the new field, which could cause issues if they are not designed to handle it.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: contributor-guide — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

Welcome, and thank you for contributing! 🎉

Great job adding comprehensive tests to validate the new startup warning functionality.

Before merging, please ensure the documentation is updated to reflect the new behavior for empty policy directories.

Refer to CONTRIBUTING.md for guidance.

@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ⚠️ Missing No current-run comment
🛡️ Security Scan ⚠️ Missing No current-run comment
🔄 Breaking Changes ⚠️ Missing No current-run comment
📝 Docs Sync ⚠️ Missing No current-run comment
🧪 Test Coverage ⚠️ Missing No current-run comment

Verdict: ⚠️ AI review incomplete; ready for human review

AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims.

@MohammadHaroonAbuomar MohammadHaroonAbuomar left a comment

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.

  • policy_server.py _load_policies: the state reset (_engine/_trust_policies/_trust_evaluator/_loaded_count) moved before the directory-exists early-return, so a hot-reload during a transient missing dir (unmounted volume) now wipes loaded policies and, with the fail-closed engine, turns the server deny-all. Availability-relevant behavior change unmentioned in the PR description; revert to preserving prior state on missing dir, or document and test the new semantics explicitly (the _trust_evaluator reset does fix a real stale-evaluator bug; state that too).

Minor:

  • _validate_startup/startup_warnings also run on every hot-reload; load_warnings is the accurate name, especially since the reload response includes the key.
  • warn-only means /ready still reports ready with zero policies; an opt-in AGT_REQUIRE_POLICIES readiness gate would let k8s catch a misconfigured policy volume before routing traffic to a deny-all sidecar. Fine as a follow-up.



def _validate_startup() -> None:
"""Record startup warnings for empty policy state."""

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.

the warning text says "governance defaults may be permissive until policies are configured", but PolicyEngine.evaluate is V26 fail-closed: no policies loaded means deny by default (and the trust endpoint 503s). An empty policy dir is deny-all, not allow-all; a warning asserting the opposite failure mode misleads operators about blast radius. Reword to the actual semantics, e.g. "all evaluations will be denied by default until policies are loaded" (same text in sidecar.py:187).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi, I updated the warning message as suggested. Also, I updated _load_policies() so a transient missing policy directory no longer clears the existing in-memory policies during hot reload, which avoids the accidental deny-all state if a mounted volume disappears briefly. The trust evaluator still gets rebuilt on real reloads, so that stale-evaluator bug is fixed as well.

I also renamed startup_warnings to load_warnings, since that warning payload is produced on both startup and reload. I left /readyz warn-only for now.

Comment on lines +267 to +281
def test_list_policies_reports_startup_warning_when_empty(self, caplog, tmp_path):
from unittest.mock import patch

with patch.dict(os.environ, {"AGENTMESH_POLICY_DIR": str(tmp_path)}):
from agentmesh.server import policy_server

with caplog.at_level(logging.WARNING):
with TestClient(policy_server.app) as client:
resp = client.get("/api/v1/policies")

assert resp.status_code == 200
data = resp.json()
assert data["total_loaded"] == 0
assert data["startup_warnings"]
assert any("Startup validation: no policies loaded" in msg for msg in caplog.messages)

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.

test_list_policies_reports_startup_warning_when_empty does not exercise what it claims: POLICY_DIR is read once at module import, the module is already imported by earlier tests, so patch.dict + re-import is a no-op and tmp_path is never used; the test passes only because /etc/agentmesh/policies does not exist in CI. Reload the module under the patched env or monkeypatch policy_server.POLICY_DIR directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi, Good catch. I changed the test so that it now actually exercises the module under test instead of depending on the already-imported default path. With that fix, the tmp_path is used during startup.

Copilot AI review requested due to automatic review settings July 29, 2026 22:41

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sakunaharinda

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@MohammadHaroonAbuomar MohammadHaroonAbuomar left a comment

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.

  • .DS_Store: a macOS Finder artifact was committed at the repo root in this push; remove it (and consider adding it to a global gitignore).

Minor:

  • all three blocking asks are resolved and verified: the warning now says evaluations are denied by default until policies load; the test monkeypatches policy_server.POLICY_DIR directly; the reload state reset is back behind the directory-exists early-return so a transient missing dir preserves loaded policies. The naming and AGT_REQUIRE_POLICIES suggestions remain optional.

Copilot AI review requested due to automatic review settings July 29, 2026 23:42

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

"status": "ready",
"component": "governance-sidecar",
"policies_loaded": _loaded_count,
"startup_warnings": list(_startup_warnings),

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.

/ready still returns status ready when zero policies are loaded, and /readyz, which is the probe most Kubernetes manifests use, is untouched and carries neither the count nor the warning. An empty PolicyEngine does deny by default, so a server in this state denies every request while reporting healthy to its orchestrator. Failing readiness when the policy count is zero would put the signal where an operator sees it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks — addressed.

Overrode /readyz in policy_server.py to return HTTP 503 when total_loaded == 0, and included total_loaded, policy_dir, and load_warnings in the response. Also set response_model=None to avoid FastAPI response-model generation errors.



# Ensure override is applied at import time
_override_readyz()

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.

agent-governance-python/agent-mesh/src/agentmesh/server/policy_server.py:74 — _override_readyz() runs after the override route is registered at :59, so it prunes BOTH /readyz routes; probed at head: GET /readyz returns 404 with 0 and with 1 loaded policy, so readiness can never pass. Please register the override after pruning (or prune by endpoint identity), add a test that /readyz returns 200 once policies load, and stop echoing POLICY_DIR in the response body.

@Ricky-G

Copy link
Copy Markdown
Contributor

Thank you for the original contribution and for the follow-up work on #3441. This PR has been open and inactive for more than a month, and it is now superseded by #4066, a clean replacement branch under Ricky-G that carries the policy-startup changes forward against current main, preserves the newer policy-load generation work, and includes the regression coverage.

To keep review and agent-merge tracking focused in one place, I am closing this PR. Please use #4066 as the maintained path for this change; it is tracked by #4061 and includes the proper Closes #4061 linkage. Thank you again for the work that started this effort.

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

Labels

agent-mesh agent-mesh package size/M Medium PR (< 200 lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants