Skip to main content

runx_contracts/
policy_proof.rs

1//! Policy-proof contracts: the authority-proof receipt envelope and its two
2//! standalone companions (`credential-envelope`, `scope-admission`).
3//!
4//! These carry the legacy bare `runx.ai/spec` `$id` (no `x-runx-schema`). They
5//! are produced by the local policy engine and guarded as wire contracts; their
6//! authoritative Rust shape lives here so the schema wire-conformance gate can cover
7//! them.
8use serde::{Deserialize, Serialize};
9
10use crate::schema::{NonEmptyString, RunxSchema};
11
12/// The kind of authority a grant or request carries.
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
14#[serde(rename_all = "snake_case")]
15pub enum AuthorityKind {
16    ReadOnly,
17    Constructive,
18    Destructive,
19}
20
21/// Whether a scope-admission decision allowed or denied the request.
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
23#[serde(rename_all = "snake_case")]
24pub enum ScopeAdmissionStatus {
25    Allow,
26    Deny,
27}
28
29/// Fixed wire identity for credential envelopes.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
31pub enum CredentialEnvelopeKind {
32    #[serde(rename = "runx.credential-envelope.v1")]
33    V1,
34}
35
36/// Fixed wire identity for authority proofs.
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
38pub enum AuthorityProofSchemaVersion {
39    #[serde(rename = "runx.authority-proof.v1")]
40    V1,
41}
42
43/// Credential-material state recorded in an authority proof.
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
45#[serde(rename_all = "snake_case")]
46pub enum AuthorityProofCredentialMaterialStatus {
47    NotRequested,
48    NotResolved,
49    Resolved,
50    Denied,
51}
52
53/// Approval-gate outcome recorded in an authority proof.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
55#[serde(rename_all = "snake_case")]
56pub enum AuthorityProofApprovalDecisionValue {
57    Approved,
58    Denied,
59}
60
61/// Fixed redaction status recorded in authority proofs.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
63pub enum AuthorityProofRedactionStatus {
64    #[serde(rename = "applied")]
65    Applied,
66}
67
68/// Fixed secret-material posture recorded in authority proofs.
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
70pub enum AuthorityProofRedactionSecretMaterial {
71    #[serde(rename = "omitted")]
72    Omitted,
73}
74
75/// Fixed stream posture recorded in authority proofs.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
77pub enum AuthorityProofRedactionStream {
78    #[serde(rename = "hashed")]
79    Hashed,
80}
81
82/// A scope-admission decision (`runx.ai/spec/scope-admission`).
83#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
84#[serde(rename_all = "snake_case", deny_unknown_fields)]
85#[runx_schema(spec_id = "https://runx.ai/spec/scope-admission.schema.json")]
86pub struct ScopeAdmission {
87    pub status: ScopeAdmissionStatus,
88    pub requested_scopes: Vec<NonEmptyString>,
89    pub granted_scopes: Vec<NonEmptyString>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub grant_id: Option<NonEmptyString>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub reasons: Option<Vec<NonEmptyString>>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub decision_summary: Option<String>,
96}
97
98/// The grant a credential or request descends from.
99#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
100#[serde(rename_all = "snake_case", deny_unknown_fields)]
101pub struct CredentialGrantReference {
102    pub grant_id: NonEmptyString,
103    pub scope_family: NonEmptyString,
104    pub authority_kind: AuthorityKind,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub target_repo: Option<NonEmptyString>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub target_locator: Option<NonEmptyString>,
109}
110
111/// A resolved credential envelope (`runx.ai/spec/credential-envelope`).
112#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
113#[serde(rename_all = "snake_case", deny_unknown_fields)]
114#[runx_schema(spec_id = "https://runx.ai/spec/credential-envelope.schema.json")]
115pub struct CredentialEnvelope {
116    pub kind: CredentialEnvelopeKind,
117    pub grant_id: NonEmptyString,
118    pub provider: NonEmptyString,
119    pub auth_mode: NonEmptyString,
120    pub material_kind: NonEmptyString,
121    pub provider_reference: NonEmptyString,
122    pub scopes: Vec<NonEmptyString>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub grant_reference: Option<CredentialGrantReference>,
125    pub material_ref: NonEmptyString,
126}
127
128/// The scopes/posture a skill requested before admission.
129#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
130#[serde(rename_all = "snake_case", deny_unknown_fields)]
131pub struct AuthorityProofRequested {
132    pub connected_auth: bool,
133    pub scopes: Vec<NonEmptyString>,
134    pub mutating: bool,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub scope_family: Option<NonEmptyString>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub authority_kind: Option<AuthorityKind>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub target_repo: Option<NonEmptyString>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub target_locator: Option<NonEmptyString>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub sandbox_profile: Option<NonEmptyString>,
145}
146
147/// The credential-material posture recorded in an authority proof.
148#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
149#[serde(rename_all = "snake_case", deny_unknown_fields)]
150pub struct AuthorityProofCredentialMaterial {
151    pub status: AuthorityProofCredentialMaterialStatus,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub grant_id: Option<NonEmptyString>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub provider: Option<NonEmptyString>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub provider_reference: Option<NonEmptyString>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub scopes: Option<Vec<NonEmptyString>>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub scope_family: Option<NonEmptyString>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub authority_kind: Option<AuthorityKind>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub target_repo: Option<NonEmptyString>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub target_locator: Option<NonEmptyString>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub grant_reference: Option<CredentialGrantReference>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub material_ref_hash: Option<NonEmptyString>,
172}
173
174/// The network posture inside a sandbox declaration.
175#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
176#[serde(rename_all = "snake_case", deny_unknown_fields)]
177pub struct AuthorityProofSandboxNetwork {
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub declared: Option<bool>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub enforcement: Option<NonEmptyString>,
182}
183
184/// The filesystem posture inside a sandbox declaration.
185#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
186#[serde(rename_all = "snake_case", deny_unknown_fields)]
187pub struct AuthorityProofSandboxFilesystem {
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub enforcement: Option<NonEmptyString>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub readonly_paths: Option<bool>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub writable_paths_enforced: Option<bool>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub private_tmp: Option<bool>,
196}
197
198/// The runtime enforcer posture inside a sandbox declaration.
199#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
200#[serde(rename_all = "snake_case", deny_unknown_fields)]
201pub struct AuthorityProofSandboxRuntime {
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub enforcer: Option<NonEmptyString>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub reason: Option<NonEmptyString>,
206}
207
208/// The sandbox posture recorded in an authority proof.
209#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
210#[serde(rename_all = "snake_case", deny_unknown_fields)]
211pub struct AuthorityProofSandbox {
212    pub profile: NonEmptyString,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub cwd_policy: Option<NonEmptyString>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub require_enforcement: Option<bool>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub network: Option<AuthorityProofSandboxNetwork>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub filesystem: Option<AuthorityProofSandboxFilesystem>,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub runtime: Option<AuthorityProofSandboxRuntime>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub approval_required: Option<bool>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub approval_approved: Option<bool>,
227}
228
229/// The decision an approval gate reached, recorded in an authority proof.
230#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
231#[serde(rename_all = "snake_case", deny_unknown_fields)]
232pub struct AuthorityProofApprovalDecision {
233    pub gate_id: NonEmptyString,
234    pub gate_type: NonEmptyString,
235    pub decision: AuthorityProofApprovalDecisionValue,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub reason: Option<NonEmptyString>,
238}
239
240/// The redaction attestation recorded in an authority proof.
241#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
242#[serde(rename_all = "snake_case", deny_unknown_fields)]
243pub struct AuthorityProofRedaction {
244    pub status: AuthorityProofRedactionStatus,
245    pub secret_material: AuthorityProofRedactionSecretMaterial,
246    pub stdout: AuthorityProofRedactionStream,
247    pub stderr: AuthorityProofRedactionStream,
248    pub metadata_secret_keys: Vec<NonEmptyString>,
249}
250
251/// The authority proof emitted alongside a skill run
252/// (`runx.ai/spec/authority-proof`): the requested posture, the scope-admission
253/// decision, the credential-material posture, and the redaction attestation.
254#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
255#[serde(rename_all = "snake_case", deny_unknown_fields)]
256#[runx_schema(spec_id = "https://runx.ai/spec/authority-proof.schema.json")]
257pub struct AuthorityProof {
258    pub schema_version: AuthorityProofSchemaVersion,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub run_id: Option<NonEmptyString>,
261    pub skill_name: NonEmptyString,
262    pub source_type: NonEmptyString,
263    pub requested: AuthorityProofRequested,
264    pub scope_admission: ScopeAdmission,
265    pub credential_material: AuthorityProofCredentialMaterial,
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub sandbox: Option<AuthorityProofSandbox>,
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub approval_gate: Option<AuthorityProofApprovalDecision>,
270    pub redaction: AuthorityProofRedaction,
271}