mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
257 lines
12 KiB
Text
257 lines
12 KiB
Text
/- Copyright (c) 2026 Sovereign Research Stack. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Research Stack Team
|
||
|
||
WebInteractionSurface.lean — Web Interaction Protocol Specification
|
||
|
||
Replaces infra/web_interaction_surface.py protocol spec with a formal Lean module.
|
||
Defines web interaction protocol, task management, and session management.
|
||
|
||
Per AGENTS.md:
|
||
- Q16_16 for scoring (§1.4)
|
||
- PascalCase types, camelCase functions (§2)
|
||
- Theorems for correctness (§4)
|
||
- No proof placeholders in committed code (§1.6)
|
||
-/
|
||
|
||
import Mathlib.Data.Nat.Basic
|
||
import Mathlib.Data.List.Basic
|
||
import Std
|
||
|
||
namespace Semantics.WebInteractionSurface
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §0 Q16_16 Fixed-Point Arithmetic
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure Q16_16 where
|
||
raw : Int
|
||
deriving Repr, DecidableEq, Inhabited
|
||
|
||
namespace Q16_16
|
||
def zero : Q16_16 := ⟨0⟩
|
||
def one : Q16_16 := ⟨65536⟩
|
||
def ofFrac (num denom : Nat) : Q16_16 :=
|
||
if denom = 0 then zero else ⟨(num * 65536) / denom⟩
|
||
end Q16_16
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §1 Duty Type Enumeration
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
inductive DutyType where
|
||
| webNavigation : DutyType
|
||
| contentExtraction : DutyType
|
||
| formInteraction : DutyType
|
||
| javascriptExecution : DutyType
|
||
| screenshotCapture : DutyType
|
||
| distributedCrawl : DutyType
|
||
deriving Repr, DecidableEq, Inhabited
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §2 Web Task Structure
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
inductive TaskStatus where
|
||
| pending : TaskStatus
|
||
| queued : TaskStatus
|
||
| executing : TaskStatus
|
||
| completed : TaskStatus
|
||
| failed : TaskStatus
|
||
deriving Repr, DecidableEq, Inhabited
|
||
|
||
structure WebTask where
|
||
taskId : String
|
||
dutyType : DutyType
|
||
url : String
|
||
priority : Nat
|
||
status : TaskStatus
|
||
assignedGpu : Option Nat
|
||
createdAt : Nat
|
||
deriving Repr, Inhabited
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §3 Browser Pool Structure
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure BrowserInfo where
|
||
sessionId : String
|
||
createdAt : Nat
|
||
healthy : Bool
|
||
deriving Repr, Inhabited
|
||
|
||
structure BrowserPool where
|
||
minBrowsers : Nat
|
||
maxBrowsers : Nat
|
||
activeBrowsers : List (String × BrowserInfo)
|
||
idleBrowsers : List String
|
||
deriving Repr, Inhabited
|
||
|
||
/-- Initialize browser pool with min/max bounds -/
|
||
def initBrowserPool (minBrowsers maxBrowsers : Nat) : BrowserPool :=
|
||
{
|
||
minBrowsers := minBrowsers,
|
||
maxBrowsers := maxBrowsers,
|
||
activeBrowsers := [],
|
||
idleBrowsers := []
|
||
}
|
||
|
||
/-- Acquire browser from pool -/
|
||
def acquireBrowser (pool : BrowserPool) (sessionId : String) : BrowserPool × Option String :=
|
||
let activeCount := pool.activeBrowsers.length
|
||
let canCreate := activeCount < pool.maxBrowsers
|
||
|
||
if canCreate then
|
||
let browserId := s!"browser_{sessionId}"
|
||
let browserInfo := { sessionId := sessionId, createdAt := 0, healthy := true }
|
||
let newPool := { pool with activeBrowsers := (browserId, browserInfo) :: pool.activeBrowsers }
|
||
(newPool, some browserId)
|
||
else if pool.idleBrowsers.isEmpty then
|
||
(pool, none)
|
||
else
|
||
let browserId := pool.idleBrowsers.head!
|
||
let browserInfo := { sessionId := sessionId, createdAt := 0, healthy := true }
|
||
let newActive := (browserId, browserInfo) :: pool.activeBrowsers
|
||
let newIdle := pool.idleBrowsers.tail!
|
||
let newPool := { pool with activeBrowsers := newActive, idleBrowsers := newIdle }
|
||
(newPool, some browserId)
|
||
|
||
/-- Release browser back to pool -/
|
||
def releaseBrowser (pool : BrowserPool) (browserId : String) : BrowserPool :=
|
||
let newActive := pool.activeBrowsers.filter (·.1 ≠ browserId)
|
||
let newIdle := if pool.activeBrowsers.any (·.1 = browserId) then browserId :: pool.idleBrowsers else pool.idleBrowsers
|
||
{ pool with activeBrowsers := newActive, idleBrowsers := newIdle }
|
||
|
||
/-- Get browser pool statistics -/
|
||
def getBrowserPoolStats (pool : BrowserPool) : (Nat × Nat × Nat × Nat) :=
|
||
(pool.activeBrowsers.length, pool.idleBrowsers.length, pool.activeBrowsers.length + pool.idleBrowsers.length, pool.maxBrowsers)
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §4 Session Management
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure BrowserSession where
|
||
sessionId : String
|
||
url : String
|
||
createdAt : Nat
|
||
lastActivity : Nat
|
||
deriving Repr, Inhabited
|
||
|
||
structure SessionManager where
|
||
sessions : List (String × BrowserSession)
|
||
ttlSeconds : Nat
|
||
deriving Repr, Inhabited
|
||
|
||
/-- Initialize session manager with TTL -/
|
||
def initSessionManager (ttlSeconds : Nat) : SessionManager :=
|
||
{ sessions := [], ttlSeconds := ttlSeconds }
|
||
|
||
/-- Create new browser session -/
|
||
def createSession (manager : SessionManager) (url : String) (currentTime : Nat) : SessionManager × String :=
|
||
let sessionId := s!"sess_{currentTime}"
|
||
let session := { sessionId := sessionId, url := url, createdAt := currentTime, lastActivity := currentTime }
|
||
let newManager := { manager with sessions := (sessionId, session) :: manager.sessions }
|
||
(newManager, sessionId)
|
||
|
||
/-- Check if session is expired -/
|
||
def isSessionExpired (session : BrowserSession) (currentTime : Nat) (ttlSeconds : Nat) : Bool :=
|
||
currentTime - session.lastActivity > ttlSeconds
|
||
|
||
/-- Get session if not expired -/
|
||
def getSession (manager : SessionManager) (sessionId : String) (currentTime : Nat) : Option BrowserSession :=
|
||
match manager.sessions.find? (·.1 = sessionId) with
|
||
| some (_, session) =>
|
||
if isSessionExpired session currentTime manager.ttlSeconds then
|
||
none
|
||
else
|
||
some session
|
||
| none => none
|
||
|
||
/-- Cleanup expired sessions -/
|
||
def cleanupExpiredSessions (manager : SessionManager) (currentTime : Nat) : SessionManager :=
|
||
let newSessions := manager.sessions.filter (fun (_, s) => ¬isSessionExpired s currentTime manager.ttlSeconds)
|
||
{ manager with sessions := newSessions }
|
||
|
||
/-- Get session manager statistics -/
|
||
def getSessionManagerStats (manager : SessionManager) : (Nat × Nat) :=
|
||
(manager.sessions.length, manager.ttlSeconds)
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §5 Task Queue Management
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
structure TaskQueue where
|
||
pendingTasks : List WebTask
|
||
completedTasks : List WebTask
|
||
deriving Repr, Inhabited
|
||
|
||
/-- Initialize empty task queue -/
|
||
def initTaskQueue : TaskQueue :=
|
||
{ pendingTasks := [], completedTasks := [] }
|
||
|
||
/-- Submit task to queue -/
|
||
def submitTask (queue : TaskQueue) (task : WebTask) : TaskQueue :=
|
||
let newQueue := { queue with pendingTasks := task :: queue.pendingTasks }
|
||
newQueue
|
||
|
||
/-- Execute task from queue -/
|
||
def executeTask (queue : TaskQueue) (taskId : String) : TaskQueue × Option WebTask :=
|
||
match queue.pendingTasks.find? (·.taskId = taskId) with
|
||
| some task =>
|
||
let executedTask := { task with status := TaskStatus.completed }
|
||
let newPending := queue.pendingTasks.filter (·.taskId ≠ taskId)
|
||
let newCompleted := executedTask :: queue.completedTasks
|
||
let newQueue := { queue with pendingTasks := newPending, completedTasks := newCompleted }
|
||
(newQueue, some executedTask)
|
||
| none => (queue, none)
|
||
|
||
/-- Get task queue statistics -/
|
||
def getTaskQueueStats (queue : TaskQueue) : (Nat × Nat) :=
|
||
(queue.pendingTasks.length, queue.completedTasks.length)
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §6 Theorems
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
/-- Empty browser pool has no active browsers -/
|
||
theorem emptyPoolHasNoActiveBrowsers (minBrowsers maxBrowsers : Nat) :
|
||
(initBrowserPool minBrowsers maxBrowsers).activeBrowsers.length = 0 := by
|
||
rfl
|
||
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
-- §7 Example Usage
|
||
-- ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
#eval let pool := initBrowserPool 2 20
|
||
let (_newPool, browserId) := acquireBrowser pool "session_123"
|
||
browserId
|
||
|
||
#eval let pool := initBrowserPool 2 20
|
||
let (_newPool, _) := acquireBrowser pool "session_123"
|
||
let (newPool2, _) := acquireBrowser pool "session_456"
|
||
getBrowserPoolStats newPool2
|
||
|
||
#eval let manager := initSessionManager 3600
|
||
let (_newManager, sessionId) := createSession manager "https://example.com" 1000
|
||
sessionId
|
||
|
||
#eval let manager := initSessionManager 3600
|
||
let (_newManager, sessionId) := createSession manager "https://example.com" 1000
|
||
getSession _newManager sessionId 1000
|
||
|
||
#eval let queue := initTaskQueue
|
||
let task := {
|
||
taskId := "task_001",
|
||
dutyType := DutyType.webNavigation,
|
||
url := "https://example.com",
|
||
priority := 5,
|
||
status := TaskStatus.pending,
|
||
assignedGpu := none,
|
||
createdAt := 1000
|
||
}
|
||
let newQueue := submitTask queue task
|
||
let (newQueue2, _) := executeTask newQueue "task_001"
|
||
getTaskQueueStats newQueue2
|
||
|
||
end Semantics.WebInteractionSurface
|