///|
/// Named policy profiles turn the analyzer into a repeatable CI gate.
pub(all) struct PolicyProfile {
name : String
reserved : Array[String]
max_chord_length : Int
max_modifier_count : Int
require_description : Bool
allow_disabled : Bool
fail_on_warning : Bool
allowed_platforms : Array[String]
} derive(Eq, @debug.Debug)
///|
pub(all) struct ProfileReport {
profile : PolicyProfile
parse : ParseResult
analysis : Analysis
metrics : KeymapMetrics
passed : Bool
gate_messages : Array[String]
} derive(Eq, @debug.Debug)
///|
fn profile(
name : String,
reserved : Array[String],
max_chord_length : Int,
max_modifier_count : Int,
require_description : Bool,
allow_disabled : Bool,
fail_on_warning : Bool,
allowed_platforms : Array[String],
) -> PolicyProfile {
{
name,
reserved,
max_chord_length,
max_modifier_count,
require_description,
allow_disabled,
fail_on_warning,
allowed_platforms,
}
}
///|
/// Sensible cross-platform defaults for desktop applications.
pub fn desktop_profile() -> PolicyProfile {
profile(
"desktop",
["all:Ctrl+Alt+Delete", "all:Cmd+Q", "all:Alt+F4"],
3,
3,
true,
true,
false,
["all", "windows", "mac", "linux"],
)
}
///|
/// A stricter profile for terminal and shell applications.
pub fn terminal_profile() -> PolicyProfile {
profile(
"terminal",
["all:Ctrl+C", "all:Ctrl+D", "all:Ctrl+Z", "all:Ctrl+\\"],
2,
2,
true,
false,
true,
["all", "windows", "mac", "linux", "freebsd"],
)
}
///|
/// A profile for user-facing tools that prioritizes discoverability.
pub fn accessible_profile() -> PolicyProfile {
profile(
"accessible",
["all:Ctrl+Alt+Delete", "all:Cmd+Q", "all:Alt+F4"],
2,
2,
true,
false,
true,
["all", "windows", "mac", "linux"],
)
}
///|
/// Resolve the built-in profile by name.
pub fn profile_by_name(name : String) -> PolicyProfile {
match lower_ascii(name) {
"terminal" => terminal_profile()
"accessible" => accessible_profile()
"strict" => {
let base = accessible_profile()
{
..base,
name: "strict",
max_chord_length: 1,
max_modifier_count: 2,
require_description: true,
}
}
_ => desktop_profile()
}
}
///|
fn profile_reserved(keymap : Keymap, profile : PolicyProfile) -> Keymap {
let markers = keymap.reserved.copy()
for marker in profile.reserved {
if !array_contains(markers, marker) {
markers.push(marker)
}
}
{ ..keymap, reserved: markers }
}
///|
fn profile_finding(
code : String,
kind : IssueKind,
severity : Severity,
message : String,
binding : Binding,
suggestion : String,
) -> Finding {
{
code,
kind,
severity,
message,
primary_id: binding.id,
secondary_id: "",
shortcut: binding.keys.canonical,
context: binding.context,
source: binding.source,
line: binding.line,
suggestion,
}
}
///|
fn profile_findings(keymap : Keymap, profile : PolicyProfile) -> Array[Finding] {
let result : Array[Finding] = []
for binding in keymap.bindings {
if profile.require_description && binding.description.length() == 0 {
result.push(
profile_finding(
"MK501",
InvalidRecord,
Warning,
"binding has no description under the selected policy profile",
binding,
"describe the user-visible action and its scope",
),
)
}
if binding.keys.steps.length() > profile.max_chord_length {
result.push(
profile_finding(
"MK502",
AccessibilityRisk,
Warning,
"chord is longer than the profile limit",
binding,
"shorten the chord or document a command-palette fallback",
),
)
}
if binding.keys.modifier_count > profile.max_modifier_count {
result.push(
profile_finding(
"MK503",
AccessibilityRisk,
Warning,
"modifier count exceeds the profile limit",
binding,
"reduce modifiers and keep a discoverable alternative",
),
)
}
if !profile.allow_disabled && !binding.enabled {
result.push(
profile_finding(
"MK504",
DisabledBinding,
Warning,
"disabled binding is not allowed in this release profile",
binding,
"remove the entry or move it to a migration-only keymap",
),
)
}
if profile.allowed_platforms.length() > 0 &&
!platform_contains_any(profile.allowed_platforms, binding.platform) {
result.push(
profile_finding(
"MK505",
InvalidRecord,
Error,
"binding targets a platform outside the profile allow-list",
binding,
"use one of the declared platforms or split the keymap",
),
)
}
}
result
}
///|
fn platform_contains_any(allowed : Array[String], platform : String) -> Bool {
for item in allowed {
if platforms_overlap(item, platform) {
return true
}
}
false
}
///|
fn append_findings(analysis : Analysis, extra : Array[Finding]) -> Analysis {
let findings = analysis.findings.copy()
for item in extra {
findings.push(item)
}
let errors = count_severity(findings, Error)
let warnings = count_severity(findings, Warning)
let infos = count_severity(findings, Info)
{
..analysis,
findings,
error_count: errors,
warning_count: warnings,
info_count: infos,
score: score_findings(findings),
}
}
///|
/// Analyze one source document with a named policy profile.
pub fn audit_with_profile(
source : String,
name : String,
profile : PolicyProfile,
) -> ProfileReport {
let parsed = parse_keymap(source, name~)
let scoped = profile_reserved(parsed.keymap, profile)
let base = analyze(scoped)
let analysis = append_findings(base, profile_findings(scoped, profile))
let metrics = keymap_metrics(scoped)
let gate_messages : Array[String] = []
if parsed.diagnostics.length() > 0 {
gate_messages.push("parser diagnostics present")
}
if analysis.error_count > 0 {
gate_messages.push(analysis.error_count.to_string() + " error findings")
}
if profile.fail_on_warning && analysis.warning_count > 0 {
gate_messages.push("warnings are fatal in profile " + profile.name)
}
let passed = parsed.ok &&
analysis.error_count == 0 &&
(!profile.fail_on_warning || analysis.warning_count == 0)
{ profile, parse: parsed, analysis, metrics, passed, gate_messages }
}
///|
/// Convenience overload using a built-in profile.
pub fn audit_with_named_profile(
source : String,
name : String,
profile_name : String,
) -> ProfileReport {
audit_with_profile(source, name, profile_by_name(profile_name))
}
///|
pub fn profile_report_to_json(report : ProfileReport) -> String {
let gates : Array[String] = []
for message in report.gate_messages {
gates.push(json_string(message))
}
"{\"profile\":" +
json_string(report.profile.name) +
",\"passed\":" +
(if report.passed { "true" } else { "false" }) +
",\"analysis\":" +
analysis_to_json(report.analysis) +
",\"metrics\":" +
metrics_to_json(report.metrics) +
",\"gates\":[" +
gates.join(",") +
"]}"
}
///|
pub fn profile_report_to_markdown(report : ProfileReport) -> String {
let lines : Array[String] = [
"## Policy profile: " + report.profile.name,
"",
"Result: **" + (if report.passed { "PASS" } else { "FAIL" }) + "**",
"",
analysis_to_markdown(report.analysis),
"",
"### Gate messages",
]
if report.gate_messages.length() == 0 {
lines.push("- none")
} else {
for message in report.gate_messages {
lines.push("- " + message)
}
}
lines.join("\n")
}