@enc-protocol/enc-app

0.2.1

Headless app framework for ENC protocol. Spec-driven development — JSON config to any platform.

npm i @enc-protocol/enc-app

enc-app

Headless app framework for the ENC protocol. Spec-driven development from JSON config to any platform.

JSON config (agent writes)
    |
    +-- validate()  --> instant correctness checks (JS, no Lean needed)
    |
    +-- loadConfig() --> runtime config for EncApp
    |
    +-- toLean()    --> complete Lean 4 file (proven by native_decide)
    |
    +-- EncApp      --> headless runtime --> platform renderer
                        |
                        +-- TUI (ink)
                        +-- Web (React/Svelte/vanilla)
                        +-- Native (React Native/SwiftUI/Jetpack)
                        +-- Desktop (Electron/Tauri)

One config. Validated instantly. Proven formally. Runs anywhere.

What it does

enc-app interprets an AppModel -- the declarative spec for an ENC app -- and provides a headless runtime. It handles state management, command execution, page navigation, RBAC enforcement, and reactive subscriptions. Platforms only render and capture input.

The AppModel defines:

  • Data fields -- what state exists (enclave:, kv_shared:, local:, smt_rbac)
  • Commands -- what can happen (mutations with RBAC-authorized event types)
  • Subscriptions -- what to watch (websocket, push, poll)
  • Pages -- where things live (logical screens with role guards)
  • Components -- what each screen shows (state, commands, subscriptions, nav edges)

The runtime enforces:

  • RBAC rules from the manifest -- platforms cannot bypass authorization
  • Navigation edges -- only declared transitions are allowed
  • Command access -- commands are scoped to the page's components

Install

import { EncApp, Emulator, loadConfig, validate, toLean } from '@enc-protocol/enc-app'

Usage

In-memory (testing, TUI)

import { EncApp, Emulator } from '@enc-protocol/enc-app'
import { config } from './apps/group/config.js'

const emu = new Emulator()
emu.initFromConfig(config)

const app = new EncApp(config.model, {
  emulator: emu,
  enclave: { identity: emu.ownerIdentity },
})

await app.start()

app.navigate('Chat')
const state = await app.state()     // { messages: [...], topic: '...' }
await app.exec('send', { content: 'hello' })

app.on('state', (s) => render(s))   // reactive updates

Network (web, native, desktop)

const app = new EncApp(model, {
  enclave: {
    nodeUrl: 'https://node1.enc.dev',
    enclaveId: 'abc...',
    identity: { pubHex, privateKey, publicKey },
  },
})

await app.start()

Custom adapter

import { EncApp } from '@enc-protocol/enc-app'

const app = new EncApp(model, {
  adapter: myNativeAdapter,  // implements fetch, submit, subscribe, connect, disconnect
})

From JSON config (agent workflow)

import { loadConfig, validate, toLean } from '@enc-protocol/enc-app'
import config from './my-app.json'

// 1. Validate (instant, no Lean compiler needed)
const result = validate(config)
// { checks: { ownerCanTerminate: true, ... }, errors: [], allPass: true }

// 2. Load into runtime format
const appConfig = loadConfig(config)
// { name, states, traits, schema, model, ... }

// 3. Transpile to Lean (deterministic, write to .lean file)
const lean = toLean(config)
// Complete Lean 4 file with types, validation theorem, codegen blocks

Architecture

EncApp (orchestrator)
|-- EncRouter (navigation state machine)
|-- EncProvider (local state + adapter routing)
|   +-- Adapter (platform-specific)
|       |-- EmulatorAdapter (testing/TUI)
|       |-- NetworkAdapter (web)
|       |-- NativeAdapter (future)
|       +-- DesktopAdapter (future)
+-- Emulator (optional, in-memory Node with real crypto)

Core

ModulePurpose
app.jsHeadless runtime -- state, commands, navigation, events
router.jsPage navigation state machine with history
provider.jsData access layer routing through adapters
adapter.jsPlatform adapter interface + built-in adapters

Config pipeline

ModulePurpose
validate.js12 runtime checks mirroring Lean's Validate.lean
config.jsJSON config -> runtime config (flatten manifest, derive roles)
transpile.jsJSON -> Lean 4 transpiler (deterministic, pure function)

Testing

ModulePurpose
emulator.jsIn-memory enclave with real crypto, real RBAC, real SMT
multi-user.jsMultiple users sharing one emulator

API

EncApp

const app = new EncApp(model, config)

await app.start()                  // connect + fetch initial state
app.stop()                         // disconnect

// State
const state = await app.state()    // { fieldName: value, ... }
app.on('state', callback)          // reactive updates

// Commands
const result = await app.exec('send', { content: 'hello' })
// { ok: true, event, receipt } or { ok: false, error: '...' }

// Navigation
app.navigate('Settings')           // -> true/false
app.back()                         // -> true/false
app.currentPage                    // 'Chat'
app.availableTargets               // ['Settings', 'Members']
app.availableCommands              // [{ name, event, role, params }]
app.components                     // current page components

// Events
app.on('state', (s) => {})         // state changed
app.on('navigate', (e) => {})      // page changed
app.on('command', (e) => {})       // command executed
app.on('error', (e) => {})         // error occurred

Adapter interface

Any platform adapter implements:

{
  fetch(field)                      // -> Promise<data>
  submit(identity, type, content)   // -> Promise<{ ok, error?, event?, receipt? }>
  subscribe(events, callback)       // register event listener
  connect()                         // establish persistent connection
  disconnect()                      // tear down
}

Validation checks

CheckWhat it verifies
ownerCanTerminateSome operator has C on Terminate
statesReachableEvery state reachable via moves or init
traitsManageableEvery trait has Grant/Revoke paths
validRanksTraits use name(rank) syntax
completeStatesMove references only declared states
validOperatorsRule operators are states/traits/contexts
noEncryptedDataViewEncrypted events not read from DataView
dataFieldsValidData fields use valid store types
commandsAuthorizedCommands reference authorized events
pagesConsistentComponents reference existing data/commands/subscriptions
hasEntryPageAt least one page marked as entry
initDefinedAt least one init entry exists

The agent workflow

Agents build apps by outputting JSON configs that match manifests/schema.json. They never touch Lean.

User describes app
    |
Agent outputs config.json
    |
validate(json)       -- instant check (milliseconds)
    |
loadConfig(json)     -- runtime config
    |
EncApp runs it       -- any platform
    |
toLean(json)         -- formal proof (write .lean, lake build proves it)

The agent stays in JSON-land where it is reliable. Lean stays in proof-land where it is deterministic. The transpiler is a pure function -- no AI, no ambiguity.

Data flow

AppModel defines:
  data: [{ name, store, encrypt }]        -- what exists
  commands: [{ name, event, role, params }] -- what can happen
  subscriptions: [{ name, source, events }] -- what to watch
  pages: [{
    name, entry, role,
    components: [{
      state: [fieldName],       -- reads from data
      commands: [cmdName],      -- dispatches commands
      subscriptions: [subName], -- listens for updates
      nav: [{ trigger, target }] -- edges to other pages
    }]
  }]

State fetch: app.state() -> collects fields from active components -> provider.fetch(field) -> adapter routes by store type -> returns data.

Command exec: app.exec(name, params) -> validates access -> adapter.submit(identity, event, params) -> emits state update.

Navigation: app.navigate(page) -> router validates -> emits navigate + state events.

Store types

PrefixSourceReturns
enclave:EventTypeEvent logArray of events with parsed content
kv_shared:keyShared KVLast value (last-write-wins)
kv_own:keyPer-identity KVLast value filtered by identity
smt_rbacRBAC stateArray of members with roles
local:keyClient memoryAnything (never hits network)

License

ENC Protocol