Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions docs/superpowers/plans/2026-05-10-applens-v1-platform-ledger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# AppLens V1 Platform Ledger Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build the AppLens-local evidence ledger and platform-host foundation without merging external apps or building MCP/A2A in v1.

**Architecture:** AppLens remains snapshot-centric. A new backend ledger writes append-only JSONL events under `%LOCALAPPDATA%\AppLens\ledger\events.jsonl` and maintains a SQLite index under `%LOCALAPPDATA%\AppLens\ledger\index.sqlite`; the desktop orchestrator appends events after scans and Tune actions. Static module manifests and status checks describe LLM, Oracle, Mailbox, and Zero without running live jobs.

**Tech Stack:** .NET, WinUI, xUnit, `Microsoft.Data.Sqlite`, JSONL, `%LOCALAPPDATA%` runtime storage.

---

## Scope

Implement now:

- Local runtime storage root resolver with `%LOCALAPPDATA%\AppLens` default.
- Evidence ledger event model with future-proof identity/scope/privacy/policy fields.
- JSONL append log as truth.
- SQLite index as rebuildable local query layer.
- Backend tests for append, readback, index creation, and corrupt-line tolerance.
- Desktop orchestration hooks for scan completion and Tune action completion.
- Static app/module manifest model and status model for LLM, Oracle, Mailbox, and Zero.
- UI additions in the current single-page dashboard for ledger storage/status and hosted-app statuses.

Defer:

- MCP adapter.
- A2A adapter.
- Full participant registry.
- Double-handshake grants.
- Cryptographic signing.
- Live Oracle research, LLM lane start/stop, Mailbox sync/send.
- Mailbox, Oracle, AppLens-LLM, or Stigmergent code edits.
- Broad Tune action expansion unless it fits safely after the ledger is complete.

## File Structure

- Create `src/AppLens.Backend/RuntimeStorage.cs`
- Resolves AppLens runtime root and ledger paths.
- Create `src/AppLens.Backend/BlackboardEvent.cs`
- Defines the ledger event contract, enums, artifact refs, provenance, and policy result.
- Create `src/AppLens.Backend/BlackboardStore.cs`
- Implements `IBlackboardStore`, append, read, SQLite index, and rebuild.
- Create `src/AppLens.Backend/ModuleManifest.cs`
- Defines static hosted-module manifest and status contracts.
- Create `src/AppLens.Backend/ModuleStatusService.cs`
- Computes blocked/available statuses without live jobs.
- Modify `src/AppLens.Backend/AppLens.Backend.csproj`
- Add `Microsoft.Data.Sqlite`.
- Modify `src/AppLens.Desktop/MainWindow.xaml`
- Add compact ledger/status section in the existing single-page dashboard.
- Modify `src/AppLens.Desktop/MainWindow.xaml.cs`
- Instantiate ledger/status services and append events after scan and Tune action completion.
- Create `tests/AppLens.Backend.Tests/BlackboardStoreTests.cs`
- TDD coverage for local ledger behavior.
- Create `tests/AppLens.Backend.Tests/ModuleStatusServiceTests.cs`
- TDD coverage for static status checks using temp dirs.
- Modify `tests/AppLens.Backend.Tests/AppLens.Backend.Tests.csproj`
- Add SQLite dependency if needed by test project restore.

## Task 1: Runtime Storage Root

**Files:**
- Create: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\RuntimeStorage.cs`
- Test: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\tests\AppLens.Backend.Tests\RuntimeStorageTests.cs`

- [ ] Write a failing test proving explicit roots create expected ledger paths.
- [ ] Run `dotnet test .\tests\AppLens.Backend.Tests\AppLens.Backend.Tests.csproj --filter RuntimeStorageTests`.
- [ ] Implement `AppLensRuntimeStorage` with `Root`, `LedgerDirectory`, `EventsJsonl`, and `IndexSqlite`.
- [ ] Run the focused test until green.

## Task 2: Ledger Event Contract

**Files:**
- Create: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\BlackboardEvent.cs`
- Test: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\tests\AppLens.Backend.Tests\BlackboardEventTests.cs`

- [ ] Write a failing test for creating a scan-completed event with schema version, participant identity, module id, app id, scope id, correlation id, data state, privacy state, and lifecycle state.
- [ ] Run the focused test and verify it fails because the model does not exist.
- [ ] Implement event records and enums.
- [ ] Run focused tests until green.

## Task 3: JSONL Append Store

**Files:**
- Create: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\BlackboardStore.cs`
- Test: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\tests\AppLens.Backend.Tests\BlackboardStoreTests.cs`

- [ ] Write a failing test that appends an event and reads it back from JSONL.
- [ ] Write a failing test that a corrupt JSONL line is skipped while valid lines still load.
- [ ] Run focused tests and verify failures.
- [ ] Implement `IBlackboardStore.AppendAsync` and `ReadAllAsync`.
- [ ] Run focused tests until green.

## Task 4: SQLite Index

**Files:**
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\AppLens.Backend.csproj`
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\BlackboardStore.cs`
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\tests\AppLens.Backend.Tests\BlackboardStoreTests.cs`

- [ ] Add `Microsoft.Data.Sqlite` to the backend project.
- [ ] Write a failing test proving appending an event creates `index.sqlite` and indexes event id, type, module id, app id, created time, data state, privacy state, and summary.
- [ ] Run focused tests and verify failure.
- [ ] Implement SQLite initialization and indexing.
- [ ] Run focused tests until green.

## Task 5: Module Manifests And Status

**Files:**
- Create: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\ModuleManifest.cs`
- Create: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Backend\ModuleStatusService.cs`
- Test: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\tests\AppLens.Backend.Tests\ModuleStatusServiceTests.cs`

- [ ] Write failing tests for Oracle blocked when repo path is absent.
- [ ] Write failing tests for Mailbox blocked when config is absent.
- [ ] Write failing tests for LLM blocked when CLI/package path is absent.
- [ ] Write failing tests for Zero blocked when no raw/import source is configured.
- [ ] Implement static manifests and file-only status checks.
- [ ] Run focused tests until green.

## Task 6: Scan Completion Ledger Hook

**Files:**
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Desktop\MainWindow.xaml.cs`
- Optional test seam: backend factory method in `BlackboardEvent.cs`

- [ ] Add a backend test for a scan-completed event factory using a small `AuditSnapshot`.
- [ ] Run focused test and verify failure.
- [ ] Implement the factory method.
- [ ] Wire desktop scan completion to append the event after `AuditService.RunAsync` returns.
- [ ] Run backend tests.

## Task 7: Tune Action Ledger Hook

**Files:**
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Desktop\MainWindow.xaml.cs`
- Optional test seam: backend factory method in `BlackboardEvent.cs`

- [ ] Add a backend test for Tune action-completed event factory using a `TuneActionRecord`.
- [ ] Run focused test and verify failure.
- [ ] Implement the factory method.
- [ ] Wire desktop Tune action completion to append one event per returned action record.
- [ ] Run backend tests.

## Task 8: Dashboard Status Section

**Files:**
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Desktop\MainWindow.xaml`
- Modify: `C:\Users\codyl\Desktop\csiOS\Projects\AppLens\src\AppLens.Desktop\MainWindow.xaml.cs`

- [ ] Add a compact storage/status section to the existing single-page dashboard.
- [ ] Show runtime root, ledger file path, indexed event count if available, and module statuses.
- [ ] Keep the current single-page shape; do not add navigation.
- [ ] Build the desktop project.

## Task 9: Verification

**Files:**
- No new files.

- [ ] Run `dotnet test C:\Users\codyl\Desktop\csiOS\Projects\AppLens\AppLensDesktop.sln`.
- [ ] Run `dotnet build C:\Users\codyl\Desktop\csiOS\Projects\AppLens\AppLensDesktop.sln`.
- [ ] Inspect `git diff --stat`.
- [ ] Confirm no files changed in AppLens-LLM, Oracle, Mailbox, or Stigmergent.

## Self-Review

This plan covers the approved v1 platform foundation: local runtime root, JSONL + SQLite ledger, future-proof schema hooks, manifest/status boundaries, scan/Tune persistence, and current UI shape. It intentionally defers MCP, A2A, full registry, double-handshake, live external jobs, and broader Tune side effects until the ledger and host contracts are stable.
1 change: 1 addition & 0 deletions src/AppLens.Backend/AppLens.Backend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageReference Include="System.Management" Version="10.0.7" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.7" />
</ItemGroup>
Expand Down
188 changes: 188 additions & 0 deletions src/AppLens.Backend/BlackboardEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using System.Text.Json.Serialization;

namespace AppLens.Backend;

public sealed class BlackboardEvent
{
public string SchemaVersion { get; init; } = "1.0";
public string EventId { get; init; } = $"evt-{Guid.NewGuid():N}";
public BlackboardEventType EventType { get; init; }
public string ParticipantIdentity { get; init; } = "applens-desktop";
public BlackboardParticipantKind ParticipantKind { get; init; } = BlackboardParticipantKind.FirstPartyModule;
public string ModuleId { get; init; } = "";
public string AppId { get; init; } = "applens-desktop";
public string ScopeId { get; init; } = "local_workstation";
public string? GrantId { get; init; }
public string CorrelationId { get; init; } = $"corr-{Guid.NewGuid():N}";
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.Now;
public BlackboardLifecycleState LifecycleState { get; init; } = BlackboardLifecycleState.Created;
public BlackboardDataState DataState { get; init; } = BlackboardDataState.Validated;
public BlackboardPrivacyState PrivacyState { get; init; } = BlackboardPrivacyState.RawPrivate;
public string Summary { get; init; } = "";
public Dictionary<string, string> Payload { get; init; } = [];
public List<BlackboardArtifactRef> ArtifactRefs { get; init; } = [];
public BlackboardProvenance Provenance { get; init; } = new();
public BlackboardPolicyResult? PolicyResult { get; init; }
public string? CanonicalHash { get; init; }
public string? SignerKeyId { get; init; }
public string? Signature { get; init; }

public static BlackboardEvent ForScanCompleted(AuditSnapshot snapshot, string? correlationId = null) =>
new()
{
EventType = BlackboardEventType.ScanCompleted,
ModuleId = "report",
CorrelationId = correlationId ?? $"corr-scan-{Guid.NewGuid():N}",
CreatedAt = snapshot.GeneratedAt,
Summary = $"Scan completed with readiness {snapshot.Readiness.Score}/100 {snapshot.Readiness.Rating}.",
Payload = new Dictionary<string, string>
{
["readiness_score"] = snapshot.Readiness.Score.ToString(),
["readiness_rating"] = snapshot.Readiness.Rating,
["finding_count"] = snapshot.Findings.Count.ToString(),
["tune_plan_count"] = snapshot.TunePlan.Count.ToString(),
["review_count"] = snapshot.Readiness.ReviewCount.ToString(),
["optional_count"] = snapshot.Readiness.OptionalCount.ToString(),
["admin_required_count"] = snapshot.Readiness.AdminRequiredCount.ToString()
},
Provenance = new BlackboardProvenance
{
Source = "AuditService.RunAsync",
Tool = "AppLens",
ToolVersion = typeof(AuditService).Assembly.GetName().Version?.ToString() ?? "unknown"
}
};

public static BlackboardEvent ForTuneAction(TuneActionRecord action, string? correlationId = null)
{
var allowed = action.Status == TuneActionStatus.Succeeded;
return new BlackboardEvent
{
EventType = BlackboardEventType.TuneActionCompleted,
ModuleId = "tune",
CorrelationId = correlationId ?? $"corr-tune-{Guid.NewGuid():N}",
CreatedAt = action.CompletedAt,
DataState = action.Status switch
{
TuneActionStatus.Succeeded => BlackboardDataState.Validated,
TuneActionStatus.Blocked => BlackboardDataState.Blocked,
TuneActionStatus.Failed => BlackboardDataState.Invalidated,
TuneActionStatus.RolledBack => BlackboardDataState.Invalidated,
_ => BlackboardDataState.Blocked
},
Summary = $"Tune action {action.Kind} for {action.Target} ended with {action.Status}.",
Payload = new Dictionary<string, string>
{
["action_id"] = action.Id,
["plan_item_id"] = action.PlanItemId,
["kind"] = action.Kind.ToString(),
["status"] = action.Status.ToString(),
["target"] = action.Target,
["message"] = action.Message,
["started_at"] = action.StartedAt.ToString("O"),
["completed_at"] = action.CompletedAt.ToString("O")
},
Provenance = new BlackboardProvenance
{
Source = "TuneActionExecutor.ExecuteAsync",
Tool = "AppLens-Tune",
ToolVersion = typeof(TuneActionExecutor).Assembly.GetName().Version?.ToString() ?? "unknown"
},
PolicyResult = new BlackboardPolicyResult
{
Allowed = allowed,
BlockedReason = allowed ? "" : action.Message,
RequiresApproval = true,
RequiresAdmin = action.RequiresAdmin,
RiskLevel = action.RequiresAdmin ? "medium" : "low",
PolicyId = "applens-tune-v1"
}
};
}
}

public sealed class BlackboardArtifactRef
{
public string Path { get; init; } = "";
public string Kind { get; init; } = "";
public string PrivacyState { get; init; } = "raw_private";
}

public sealed class BlackboardProvenance
{
public string Source { get; init; } = "";
public string Tool { get; init; } = "";
public string ToolVersion { get; init; } = "";
public string Command { get; init; } = "";
public string NodeId { get; init; } = Environment.MachineName;
public string ModelId { get; init; } = "";
public List<string> InputRefs { get; init; } = [];
}

public sealed class BlackboardPolicyResult
{
public bool Allowed { get; init; }
public string BlockedReason { get; init; } = "";
public bool RequiresApproval { get; init; }
public bool RequiresAdmin { get; init; }
public string RiskLevel { get; init; } = "low";
public string PolicyId { get; init; } = "";
}

[JsonConverter(typeof(JsonStringEnumConverter<BlackboardEventType>))]
public enum BlackboardEventType
{
CapabilityObserved,
EvidenceCaptured,
ReportGenerated,
ActionProposed,
ActionApproved,
ActionExecuted,
VerificationRecorded,
ModelRunRecorded,
BlockedState,
ScanCompleted,
TuneActionCompleted
}

[JsonConverter(typeof(JsonStringEnumConverter<BlackboardParticipantKind>))]
public enum BlackboardParticipantKind
{
FirstPartyModule,
NodeAgent,
McpApp,
Connector,
HumanOperator,
System,
ThirdPartyAgent
}

[JsonConverter(typeof(JsonStringEnumConverter<BlackboardLifecycleState>))]
public enum BlackboardLifecycleState
{
Created,
Imported,
Validated,
UsedInReport,
Invalidated,
Expired
}

[JsonConverter(typeof(JsonStringEnumConverter<BlackboardDataState>))]
public enum BlackboardDataState
{
Validated,
Fixture,
Stale,
Blocked,
Invalidated,
Unavailable
}

[JsonConverter(typeof(JsonStringEnumConverter<BlackboardPrivacyState>))]
public enum BlackboardPrivacyState
{
RawPrivate,
Sanitized,
Exportable
}
Loading
Loading