kernel/event/record/skill.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What shelving a skill and auditing it record
7//! (`crates/kernel/spec/Event/Record.lean` D23).
8//!
9//! The line is record-only: what an auditor said about a skill decides
10//! no byte of a model request. A skill's use is not recorded here; the
11//! `tool_called` lines that read it already say so.
12
13use serde::{Deserialize, Serialize};
14
15use crate::B3Hash;
16
17/// `skill_audited`: one audit of one version of a skill.
18///
19/// The digest binds the verdict to the bytes it judged: once the skill's
20/// content changes, its digest changes and this line no longer speaks
21/// for it.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
24pub struct SkillAudited {
25 /// Why this content has no registered source to request a remote audit.
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub local_only_reason: Option<String>,
28 pub skill: String,
29 pub digest: B3Hash,
30 pub source: AuditSource,
31 /// The auditor's own version or name; for skills.sh, the partner,
32 /// such as `socket`.
33 pub scanner: String,
34 pub verdict: AuditVerdict,
35 /// The risk level in the auditor's own word.
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub risk: Option<String>,
38 /// When the auditor says it audited, in its own spelling.
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub audited_at: Option<String>,
41 /// Where a person reads the audit when the city could not fetch it.
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub link: Option<String>,
44}
45
46/// `skill_shelved`: one version of a skill landed on a shelf through the
47/// city's install door.
48///
49/// Written only when bytes actually landed: a reinstall of the same
50/// content changes nothing and writes nothing. `digest` is the value the
51/// audit binds to, so a version, its shelving and its audits meet on one
52/// digest.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55pub struct SkillShelved {
56 pub skill: String,
57 pub digest: B3Hash,
58 pub source: ShelvedFrom,
59}
60
61/// Where a shelved version came from.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(tag = "kind", rename_all = "snake_case")]
64#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
65pub enum ShelvedFrom {
66 /// A directory or document on this machine.
67 Path,
68 /// A clone of an `https://` repository at a revision.
69 Git { url: String, rev: Option<String> },
70 /// A skill skills.sh names as `<owner>/<repo>/<skill>`.
71 SkillsSh { name: String },
72 /// One of the skills the binary carries.
73 Shipped,
74 /// Text written on the shelf page.
75 Page,
76}
77
78/// Who audited.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82pub enum AuditSource {
83 /// The partner audits skills.sh publishes for a skill.
84 SkillsSh,
85 /// The SkillSpector scanner run on this machine.
86 SkillSpector,
87}
88
89/// What the audit concluded.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
93pub enum AuditVerdict {
94 Pass,
95 Warn,
96 Fail,
97 /// An auditor applied and no audit could be had: skills.sh did not
98 /// answer or refused, or SkillSpector exited with neither verdict code.
99 /// Shelving goes ahead, and the audit state never counts this line as
100 /// an audit. When no auditor applies - a local or shipped skill and no
101 /// SkillSpector on the PATH - no line is written at all (city D19).
102 Unreachable,
103}