Identify a process instance by a deterministic identity returned
from the agent (per-OS, with a Process.mainModule + pid fallback)
and stash it on ProcessSession.processInfo. Rekey memory_page
into memory_page_module (module UUID + offset) so cached bytes
survive ASLR, and memory_page_anon (process identity + address)
for JIT / heap regions that only survive within one process
instance. Purge stale anon pages on every fresh attach.
Add updateProcessInfo, updateModuleAnalysis and updateInsight
SessionOps plus a side-channel publish-memory-page op and a
+fetch-memory-page request. The driver write-throughs every
page it fetches; non-drivers pull on miss and persist locally.
Module-analysis rows flow the same way so non-drivers can key
their cache by module UUID. The lab-join snapshot now carries
processInfo and module_analyses too.
Refresh actions on the insight detail view: Reread Bytes drops
the pages overlapping the visible range; Reanalyze Module drops
the module's analysis and pages, re-running r2 on next access.
The macOS detail view now reads the insight live from
insightsBySession via observation; GTK forwards .insightUpdated
into the detail and reschedules a refresh when the resolved
address changes.
Engine drives device.enableSpawnGating() per armed-session intent;
matched spawns re-attach via the existing attach path and misses
resume immediately. Engagement intent is in-memory only so launches
don't auto-engage gating for sessions armed in a previous run.
ProcessSession gains armingState and lastArmPattern, mirrored in
the SQLite schema. RuntimeEvent.Source.spawnGating carries the
captured/released outcome with wire encoding.
Lab protocol adds update-arming as a SessionOp variant; armed-only
sessions announce themselves with pid 0 and phase detached so peers
can see them before any capture.
Engine.onUserNotification surfaces gating-enable failures using the
clean Frida.Error message.
Drop the dual ProcessModule/PersistedModule split and reuse the
live types for lastKnownModules/lastKnownThreads. Survives detach
and decouples persistence shape from a one-feature struct.
Sessions in a lab are now collaborative artifacts. Any owner can
host (own the local Frida attachment) and any owner can drive
(issue REPL evals and instrument commands); a driver's actions
are relayed to the current host. Members watch read-only and see
an "Only lab owners can host sessions" CTA when they try the new
session button.
Re-host (Run on My Device…) lets an owner take over a running
session by attaching to their own equivalent process; the prior
host's Frida attachment detaches and the new host auto-loads the
session's instruments. Driver auto-reverts to host when the
current driver fully disconnects from the lab.
All model structs now conform to FetchableRecord +
PersistableRecord with snake_case table/column names.
GRDB handles Codable encoding/decoding automatically.
ProjectStore rewritten to use the type-safe query builder:
model.save(db), Model.fetchAll(db), Model.filter(...),
Model.deleteOne(db, key:) — no raw SQL remaining.
Define all domain model structs as plain Codable types in
LumaCore, replacing the SwiftData @Model classes:
ProcessSession, InstrumentInstance, REPLCell, NotebookEntry,
ITraceCaptureRecord, AddressInsight, InstalledPackage,
ProjectPackagesState, ProjectCollaborationState,
TargetPickerState, RemoteDeviceConfig
Flesh out ProjectStore with complete GRDB/SQLite CRUD for
all 11 tables. Both the SwiftUI and GTK frontends will use
this as their persistence layer.