Skip to main content

agent_policy/model/
policy.rs

1// Raw policy types — directly deserialized from YAML.
2// Do not use these types in renderers — use the normalized model instead.
3
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7/// Raw deserialized policy as it appears in agent-policy.yaml.
8///
9/// `Serialize` is required so `serde_json::to_value(&raw)` compiles in the load pipeline.
10/// All `Option` fields use `skip_serializing_if` so `None` values are **omitted** from the
11/// JSON rather than serialized as `null` — the JSON Schema uses plain type checks (not
12/// `["T", "null"]`), so a `null` value would fail validation.
13#[derive(Debug, Deserialize, Serialize)]
14#[serde(deny_unknown_fields)]
15pub struct RawPolicy {
16    /// Declares the schema version. Use `"1"` for all new files.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub schema_version: Option<String>,
19    pub project: RawProject,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub commands: Option<RawCommands>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub paths: Option<RawPaths>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub roles: Option<IndexMap<String, RawRole>>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub constraints: Option<RawConstraints>,
28    /// List of output target IDs.
29    /// Valid values: `"agents-md"`, `"claude-md"`, `"cursor-rules"`, `"gemini-md"`, `"copilot-instructions"`.
30    /// Defaults to `["agents-md"]` when omitted.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub outputs: Option<Vec<String>>,
33}
34
35#[derive(Debug, Deserialize, Serialize)]
36#[serde(deny_unknown_fields)]
37pub struct RawProject {
38    pub name: String,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub summary: Option<String>,
41}
42
43#[derive(Debug, Default, Deserialize, Serialize)]
44#[serde(deny_unknown_fields)]
45pub struct RawCommands {
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub install: Option<String>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub dev: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub lint: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub test: Option<String>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub build: Option<String>,
56}
57
58#[derive(Debug, Default, Deserialize, Serialize)]
59#[serde(deny_unknown_fields)]
60pub struct RawPaths {
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub editable: Option<Vec<String>>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub protected: Option<Vec<String>>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub generated: Option<Vec<String>>,
67}
68
69#[derive(Debug, Default, Deserialize, Serialize)]
70#[serde(deny_unknown_fields)]
71pub struct RawRole {
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub editable: Option<Vec<String>>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub forbidden: Option<Vec<String>>,
76}
77
78#[derive(Debug, Default, Deserialize, Serialize)]
79#[serde(deny_unknown_fields)]
80pub struct RawConstraints {
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub require_tests_for_code_changes: Option<bool>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub forbid_secrets: Option<bool>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub require_human_review_for_protected_paths: Option<bool>,
87}