move docs to docs/ folder, merge architecture files, update references

This commit is contained in:
2026-01-19 09:02:11 +01:00
parent 1b34b0e974
commit 010e11cf6b
68 changed files with 1741 additions and 1350 deletions

667
docs/APP_SPECS.md Normal file
View File

@@ -0,0 +1,667 @@
# Mosis App Specifications
This document defines the complete specification for Mosis apps, including package format, manifest schema, permissions, and available APIs.
---
## Package Format
Mosis apps are distributed as `.mosis` files, which are signed ZIP archives.
### File Structure
```
com.developer.appname-1.0.0.mosis
├── manifest.json # App metadata (required)
├── META-INF/
│ ├── MANIFEST.MF # SHA-256 hashes of all files
│ ├── CERT.PEM # Developer's public key
│ └── CERT.SIG # Ed25519 signature of MANIFEST.MF
├── assets/
│ ├── main.rml # Entry point (required)
│ ├── screens/ # Additional RML screens
│ ├── styles/ # RCSS stylesheets
│ └── scripts/ # Lua scripts
├── icons/
│ ├── icon-32.png # 32x32 icon
│ ├── icon-64.png # 64x64 icon
│ └── icon-128.png # 128x128 icon
└── locales/ # Optional i18n files
├── en.json
└── es.json
```
### Size Limits
| Limit | Value |
|-------|-------|
| Max package size | 50 MB |
| Max individual file | 10 MB |
| Max files count | 1000 |
| Max path length | 256 chars |
| Max manifest size | 64 KB |
### Allowed File Types
| Category | Extensions |
|----------|------------|
| UI | `.rml` |
| Styles | `.rcss` |
| Scripts | `.lua` |
| Images | `.png`, `.jpg`, `.jpeg`, `.tga`, `.webp` |
| Fonts | `.ttf`, `.otf` |
| Data | `.json` |
| Audio | `.ogg`, `.wav`, `.mp3` |
**Forbidden**: `.exe`, `.dll`, `.so`, `.dylib`, `.sh`, `.bat`, `.ps1`, `.py`, `.js`, nested archives
---
## Manifest Schema
### Required Fields
```json
{
"$schema": "https://mosis.dev/schemas/manifest-v1.json",
"id": "com.developer.appname",
"name": "My App",
"version": "1.0.0",
"version_code": 1,
"entry": "assets/main.rml",
"min_mosis_version": "1.0.0"
}
```
### Full Schema
```json
{
"$schema": "https://mosis.dev/schemas/manifest-v1.json",
"id": "com.developer.appname",
"name": "My App",
"version": "1.0.0",
"version_code": 1,
"description": "A short description of the app",
"entry": "assets/main.rml",
"author": {
"name": "Developer Name",
"email": "dev@example.com",
"url": "https://developer.com"
},
"permissions": [
"storage",
"network.internet",
"camera"
],
"icons": {
"32": "icons/icon-32.png",
"64": "icons/icon-64.png",
"128": "icons/icon-128.png"
},
"min_mosis_version": "1.0.0",
"target_mosis_version": "1.2.0",
"category": "utilities",
"tags": ["productivity", "tools"],
"orientation": "portrait",
"background_color": "#FFFFFF",
"locales": ["en", "es", "fr"],
"default_locale": "en",
"network": {
"allowed_domains": [
"api.example.com",
"*.cdn.example.com"
],
"allow_http": false,
"max_connections": 10
}
}
```
### Field Reference
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | Yes | Unique package ID (reverse domain: `^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$`) |
| `name` | string | Yes | Display name (max 30 chars) |
| `version` | string | Yes | Semantic version (X.Y.Z) |
| `version_code` | integer | Yes | Incremental build number (must increase with each release) |
| `entry` | string | Yes | Path to entry RML file |
| `min_mosis_version` | string | Yes | Minimum Mosis version required |
| `description` | string | No | Short description (max 80 chars) |
| `author` | object | No | Author information |
| `permissions` | array | No | Required permissions |
| `icons` | object | No | Icon paths by size |
| `category` | string | No | App store category |
| `tags` | array | No | Searchable tags |
| `orientation` | string | No | `portrait`, `landscape`, `any` |
| `background_color` | string | No | Hex color for loading screen |
| `locales` | array | No | Supported locale codes |
---
## Signing
### Algorithm
- **Key type**: Ed25519
- **Signature size**: 64 bytes
- **Hash**: SHA-256 for file manifests
### MANIFEST.MF Format
```
Manifest-Version: 1.0
Created-By: mosis-cli 1.0.0
Name: manifest.json
SHA-256-Digest: base64encodedHash==
Name: assets/main.rml
SHA-256-Digest: base64encodedHash==
Name: assets/scripts/main.lua
SHA-256-Digest: base64encodedHash==
```
### Signing Flow
1. Generate `MANIFEST.MF` with SHA-256 hash of each file
2. Sign `MANIFEST.MF` with developer's Ed25519 private key
3. Store signature in `META-INF/CERT.SIG`
4. Include developer's public key in `META-INF/CERT.PEM`
### Verification Flow
1. Extract `META-INF/MANIFEST.MF`
2. Verify signature using `CERT.PEM`
3. Verify `CERT.PEM` is registered with developer account
4. Verify each file hash matches `MANIFEST.MF`
---
## Permissions
Apps must declare required permissions in the manifest. Some permissions are auto-granted, others require user approval.
### Permission Categories
| Permission | Description | Risk Level |
|------------|-------------|------------|
| `storage` | App-private file access | Normal (auto-granted) |
| `network.internet` | HTTP requests | Dangerous |
| `network.websocket` | WebSocket connections | Dangerous |
| `camera` | Camera access | Dangerous |
| `microphone` | Audio recording | Dangerous |
| `location.coarse` | Approximate location | Dangerous |
| `location.fine` | Precise GPS location | Dangerous |
| `contacts.read` | Read contacts | Dangerous |
| `contacts.write` | Modify contacts | Dangerous |
| `bluetooth` | Bluetooth access | Dangerous |
| `sensors.body` | Body sensors (heart rate) | Dangerous |
| `clipboard.read` | Read clipboard | Dangerous |
| `clipboard.write` | Write to clipboard | Dangerous |
| `system.notifications` | Show notifications | Normal |
### Requesting Permissions
```lua
-- Request a dangerous permission (shows system dialog)
local granted = permissions.request("camera")
if granted then
-- Permission granted, can now use camera API
camera.start()
else
-- Permission denied, show fallback UI
showPermissionDeniedMessage()
end
```
### User Gesture Requirements
Dangerous hardware permissions (`camera`, `microphone`, `location`) require a recent user gesture (within 5 seconds) before the permission request is shown.
---
## Runtime Limits
Apps run in a sandboxed Lua environment with resource limits.
### Memory Limits
| Resource | Limit |
|----------|-------|
| Total memory | 16 MB |
| Max string size | 1 MB |
| Max table entries | 100,000 |
### CPU Limits
| Context | Instruction Limit | Approx Time |
|---------|-------------------|-------------|
| Event handler | 1,000,000 | ~10ms |
| Init/load | 10,000,000 | ~100ms |
| Timer callback | 500,000 | ~5ms |
### Storage Limits
| Resource | Limit |
|----------|-------|
| App storage | 100 MB |
| Cache storage | 50 MB |
| Single file | 50 MB |
| Files per directory | 10,000 |
| SQLite database | 50 MB |
---
## Lua APIs
### Safe Built-in Globals
```lua
-- Retained from standard Lua
print -- Redirected to system logger
type, tonumber, tostring
pairs, ipairs, next
pcall, xpcall
assert, error
select, unpack
string.* -- Except string.dump
table.*
math.*
utf8.*
```
### Removed Globals (Security)
```lua
-- These do NOT exist in the sandbox
dofile, loadfile, load, loadstring
rawget, rawset, rawequal, rawlen
getmetatable, setmetatable
collectgarbage
os, io, debug, package, require
ffi, jit, newproxy
```
---
## Storage API
```lua
-- App-private storage (auto-granted)
storage.write("data/config.json", json.encode(config))
local data = storage.read("data/config.json")
local exists = storage.exists("data/config.json")
storage.delete("data/config.json")
storage.mkdir("data/subfolder")
local files = storage.list("data/")
-- Cache storage (can be cleared by system)
storage.write("cache/temp.dat", data)
-- Temp storage (cleared on app stop)
storage.write("temp/session.dat", data)
```
---
## Database API (SQLite)
```lua
-- Open database (creates if not exists)
local db = database.open("mydata.db")
-- Execute SQL
db:exec([[
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)
]])
-- Prepared statements with parameters
local stmt = db:prepare("INSERT INTO users (name, email) VALUES (?, ?)")
stmt:bind("John Doe", "john@example.com")
stmt:exec()
-- Query with results
local rows = db:query("SELECT * FROM users WHERE name LIKE ?", "%John%")
for _, row in ipairs(rows) do
print(row.id, row.name, row.email)
end
-- Close when done
db:close()
```
---
## Network API
### HTTP Requests
```lua
-- Requires "network.internet" permission
local response = network.request({
url = "https://api.example.com/data",
method = "POST", -- GET, POST, PUT, DELETE, PATCH
headers = {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer " .. token
},
body = json.encode({key = "value"}),
timeout = 30000 -- milliseconds
})
-- Response structure
if response.error then
print("Error:", response.error)
else
print("Status:", response.status) -- 200, 404, etc.
print("Body:", response.body) -- Response body string
-- response.headers: table of headers
end
```
### WebSocket
```lua
-- Requires "network.websocket" permission
local ws = network.websocket("wss://api.example.com/ws")
ws:on("open", function()
ws:send(json.encode({type = "hello"}))
end)
ws:on("message", function(data)
local msg = json.decode(data)
print("Received:", msg.type)
end)
ws:on("close", function(code, reason)
print("Closed:", code, reason)
end)
ws:on("error", function(err)
print("Error:", err)
end)
-- Send data
ws:send("Hello, server!")
-- Close connection
ws:close()
```
---
## Camera API
```lua
-- Requires "camera" permission + user gesture
local granted = permissions.request("camera")
if not granted then return end
-- Start camera preview
camera.start({
facing = "back", -- "back" or "front"
resolution = "720p", -- "480p", "720p", "1080p"
target = "preview" -- Element ID to render preview
})
-- Capture photo
camera.capture(function(image)
-- image.data: base64 encoded JPEG
-- image.width, image.height: dimensions
storage.write("photos/capture.jpg", base64.decode(image.data))
end)
-- Stop camera
camera.stop()
```
---
## Microphone API
```lua
-- Requires "microphone" permission + user gesture
local granted = permissions.request("microphone")
if not granted then return end
-- Start recording
microphone.start({
format = "wav", -- "wav" or "ogg"
sample_rate = 44100,
channels = 1
})
-- Stop and get recording
microphone.stop(function(audio)
-- audio.data: base64 encoded audio
-- audio.duration: length in seconds
storage.write("recordings/voice.wav", base64.decode(audio.data))
end)
```
---
## Location API
```lua
-- Requires "location.fine" or "location.coarse" permission
local granted = permissions.request("location.fine")
if not granted then return end
-- Get current location
location.get(function(pos)
if pos.error then
print("Error:", pos.error)
else
print("Lat:", pos.latitude)
print("Lon:", pos.longitude)
print("Accuracy:", pos.accuracy) -- meters
end
end)
-- Watch location changes
local watch_id = location.watch(function(pos)
print("Location updated:", pos.latitude, pos.longitude)
end, {
interval = 5000, -- minimum time between updates (ms)
distance = 10 -- minimum distance change (meters)
})
-- Stop watching
location.unwatch(watch_id)
```
---
## Sensors API
```lua
-- Motion sensors (no permission required)
sensors.accelerometer.start(function(data)
print("Accel:", data.x, data.y, data.z)
end, { interval = 100 }) -- ms
sensors.gyroscope.start(function(data)
print("Gyro:", data.x, data.y, data.z)
end)
sensors.magnetometer.start(function(data)
print("Mag:", data.x, data.y, data.z)
end)
-- Stop sensor
sensors.accelerometer.stop()
-- Body sensors (requires "sensors.body" permission)
local granted = permissions.request("sensors.body")
if granted then
sensors.heartrate.start(function(data)
print("Heart rate:", data.bpm)
end)
end
```
---
## Timer API
```lua
-- One-shot timer
local timer_id = timer.after(1000, function()
print("1 second elapsed")
end)
-- Repeating timer
local interval_id = timer.every(500, function()
print("Every 500ms")
end)
-- Cancel timers
timer.cancel(timer_id)
timer.cancel(interval_id)
```
---
## JSON API
```lua
-- Encode Lua table to JSON string
local json_str = json.encode({
name = "John",
age = 30,
active = true,
tags = {"lua", "mosis"}
})
-- Decode JSON string to Lua table
local data = json.decode(json_str)
print(data.name) -- "John"
```
---
## Crypto API
```lua
-- Hashing
local hash = crypto.sha256("hello world")
local hash = crypto.sha512("hello world")
local hash = crypto.md5("hello world") -- Not for security
-- HMAC
local mac = crypto.hmac("sha256", "secret key", "message")
-- Random bytes
local random = crypto.random(32) -- 32 random bytes (base64)
-- UUID generation
local uuid = crypto.uuid() -- "550e8400-e29b-41d4-a716-446655440000"
```
---
## Navigation
```lua
-- Navigate to another screen
navigateTo("screens/settings.rml")
-- Go back
goBack()
-- Go to home screen
goHome()
-- Get current screen
local screen = getCurrentScreen()
```
---
## App Categories
Valid categories for the `category` manifest field:
| Category | Description |
|----------|-------------|
| `utilities` | Tools and utilities |
| `productivity` | Work and productivity |
| `communication` | Messaging and social |
| `entertainment` | Games and media |
| `lifestyle` | Health, fitness, lifestyle |
| `finance` | Banking and finance |
| `education` | Learning and reference |
| `news` | News and magazines |
| `travel` | Travel and navigation |
| `shopping` | Shopping and commerce |
---
## Error Handling
```lua
-- Safe API calls with pcall
local ok, result = pcall(function()
return network.request({url = "https://api.example.com"})
end)
if not ok then
print("Error:", result)
end
-- API errors return error field
local response = network.request({url = "https://invalid"})
if response.error then
print("Network error:", response.error)
end
```
---
## Best Practices
### Do
- Request only the permissions you need
- Handle permission denials gracefully
- Cache network responses when appropriate
- Clean up timers and resources when done
- Use try/catch patterns for error handling
- Store sensitive data encrypted
### Don't
- Request permissions before they're needed
- Store sensitive data in plain text
- Make unnecessary network requests
- Hold camera/microphone open unnecessarily
- Create infinite loops or excessive recursion
- Assume network is always available
---
## References
- [RmlUi Documentation](https://mikke89.github.io/RmlUiDoc/)
- [Lua 5.4 Reference Manual](https://www.lua.org/manual/5.4/)
- [Ed25519 Specification](https://ed25519.cr.yp.to/)