|
| 1 | +-- SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | +||| K9iserMcp.SafeK9iser: Formally verified K9-contract generation pipeline. |
| 4 | +||| |
| 5 | +||| Cartridge: k9iser-mcp |
| 6 | +||| Matrix cell: config domain x {MCP, REST} protocols |
| 7 | +||| |
| 8 | +||| This module defines a regeneration pipeline state machine that prevents: |
| 9 | +||| - Applying (committing) contracts that were never validated |
| 10 | +||| - Validating contracts that were never generated |
| 11 | +||| - Generating from an unloaded manifest |
| 12 | +||| |
| 13 | +||| State machine: Empty -> ManifestLoaded -> Generated -> Validated -> Applied |
| 14 | +module K9iserMcp.SafeK9iser |
| 15 | + |
| 16 | +import Data.List |
| 17 | + |
| 18 | +%default total |
| 19 | + |
| 20 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 21 | +-- K9iser Pipeline State Machine |
| 22 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 23 | + |
| 24 | +||| k9iser regeneration lifecycle states. |
| 25 | +||| Progresses: Empty -> ManifestLoaded -> Generated -> Validated -> Applied |
| 26 | +public export |
| 27 | +data K9State |
| 28 | + = Empty |
| 29 | + | ManifestLoaded |
| 30 | + | Generated |
| 31 | + | Validated |
| 32 | + | Applied |
| 33 | + | K9Error |
| 34 | + |
| 35 | +||| Equality for K9 states. |
| 36 | +public export |
| 37 | +Eq K9State where |
| 38 | + Empty == Empty = True |
| 39 | + ManifestLoaded == ManifestLoaded = True |
| 40 | + Generated == Generated = True |
| 41 | + Validated == Validated = True |
| 42 | + Applied == Applied = True |
| 43 | + K9Error == K9Error = True |
| 44 | + _ == _ = False |
| 45 | + |
| 46 | +||| Valid state transitions (enforced at the type level). |
| 47 | +||| Critically, Generated -> Applied is NOT valid (must validate first). |
| 48 | +||| And ManifestLoaded -> Validated is NOT valid (must generate first). |
| 49 | +public export |
| 50 | +data ValidTransition : K9State -> K9State -> Type where |
| 51 | + LoadManifest : ValidTransition Empty ManifestLoaded |
| 52 | + Generate : ValidTransition ManifestLoaded Generated |
| 53 | + Regenerate : ValidTransition Generated Generated |
| 54 | + Validate : ValidTransition Generated Validated |
| 55 | + Apply : ValidTransition Validated Applied |
| 56 | + CleanApplied : ValidTransition Applied Empty |
| 57 | + CleanReady : ValidTransition Validated Empty |
| 58 | + ManifestErr : ValidTransition ManifestLoaded K9Error |
| 59 | + GenerateErr : ValidTransition Generated K9Error |
| 60 | + Recover : ValidTransition K9Error Empty |
| 61 | + |
| 62 | +||| Runtime transition validator (matches the Zig FFI isValidTransition). |
| 63 | +public export |
| 64 | +canTransition : K9State -> K9State -> Bool |
| 65 | +canTransition Empty ManifestLoaded = True |
| 66 | +canTransition ManifestLoaded Generated = True |
| 67 | +canTransition Generated Generated = True -- regenerate |
| 68 | +canTransition Generated Validated = True |
| 69 | +canTransition Validated Applied = True |
| 70 | +canTransition Applied Empty = True -- clean |
| 71 | +canTransition Validated Empty = True -- clean without applying |
| 72 | +canTransition ManifestLoaded K9Error = True -- parse error |
| 73 | +canTransition Generated K9Error = True -- generation error |
| 74 | +canTransition K9Error Empty = True -- recover |
| 75 | +canTransition _ _ = False |
| 76 | + |
| 77 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 78 | +-- Config Format Types |
| 79 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 80 | + |
| 81 | +||| Config formats k9iser can wrap (mirrors k9iser's ConfigFormat). |
| 82 | +public export |
| 83 | +data K9Format |
| 84 | + = Toml |
| 85 | + | Yaml |
| 86 | + | Json |
| 87 | + | Ini |
| 88 | + | Custom String |
| 89 | + |
| 90 | +||| C-ABI encoding. |
| 91 | +public export |
| 92 | +formatToInt : K9Format -> Int |
| 93 | +formatToInt Toml = 1 |
| 94 | +formatToInt Yaml = 2 |
| 95 | +formatToInt Json = 3 |
| 96 | +formatToInt Ini = 4 |
| 97 | +formatToInt (Custom _) = 99 |
| 98 | + |
| 99 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 100 | +-- MCP Tool Definitions |
| 101 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 102 | + |
| 103 | +||| MCP tools exposed by this cartridge. |
| 104 | +public export |
| 105 | +data McpTool |
| 106 | + = ToolLoadManifest -- Load the k9iser.toml manifest |
| 107 | + | ToolGenerate -- Generate K9 contracts from configs |
| 108 | + | ToolValidate -- Validate contracts (K9! magic + pedigree) |
| 109 | + | ToolApply -- Commit + push regenerated contracts |
| 110 | + | ToolClean -- Clean the session |
| 111 | + | ToolStatus -- Pipeline health check |
| 112 | + |
| 113 | +||| MCP tool name (for JSON-RPC method name). |
| 114 | +public export |
| 115 | +toolName : McpTool -> String |
| 116 | +toolName ToolLoadManifest = "k9/load_manifest" |
| 117 | +toolName ToolGenerate = "k9/generate" |
| 118 | +toolName ToolValidate = "k9/validate" |
| 119 | +toolName ToolApply = "k9/apply" |
| 120 | +toolName ToolClean = "k9/clean" |
| 121 | +toolName ToolStatus = "k9/status" |
| 122 | + |
| 123 | +||| Which tools require generated contracts already present. |
| 124 | +||| Validate and Apply both require a successful generate first. |
| 125 | +public export |
| 126 | +toolRequiresGenerate : McpTool -> Bool |
| 127 | +toolRequiresGenerate ToolValidate = True |
| 128 | +toolRequiresGenerate ToolApply = True |
| 129 | +toolRequiresGenerate _ = False |
| 130 | + |
| 131 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 132 | +-- C-ABI Exports |
| 133 | +-- ═══════════════════════════════════════════════════════════════════════════ |
| 134 | + |
| 135 | +||| K9 state to integer. |
| 136 | +public export |
| 137 | +k9StateToInt : K9State -> Int |
| 138 | +k9StateToInt Empty = 0 |
| 139 | +k9StateToInt ManifestLoaded = 1 |
| 140 | +k9StateToInt Generated = 2 |
| 141 | +k9StateToInt Validated = 3 |
| 142 | +k9StateToInt Applied = 4 |
| 143 | +k9StateToInt K9Error = 5 |
| 144 | + |
| 145 | +||| FFI: Validate a state transition. |
| 146 | +export |
| 147 | +k9_can_transition : Int -> Int -> Int |
| 148 | +k9_can_transition from to = |
| 149 | + let fromState = case from of |
| 150 | + 0 => Empty |
| 151 | + 1 => ManifestLoaded |
| 152 | + 2 => Generated |
| 153 | + 3 => Validated |
| 154 | + 4 => Applied |
| 155 | + _ => K9Error |
| 156 | + toState = case to of |
| 157 | + 0 => Empty |
| 158 | + 1 => ManifestLoaded |
| 159 | + 2 => Generated |
| 160 | + 3 => Validated |
| 161 | + 4 => Applied |
| 162 | + _ => K9Error |
| 163 | + in if canTransition fromState toState then 1 else 0 |
| 164 | + |
| 165 | +||| FFI: Check if a tool requires generated contracts. |
| 166 | +export |
| 167 | +k9_tool_requires_generate : Int -> Int |
| 168 | +k9_tool_requires_generate 2 = 1 -- ToolValidate |
| 169 | +k9_tool_requires_generate 3 = 1 -- ToolApply |
| 170 | +k9_tool_requires_generate _ = 0 -- All others do not |
0 commit comments