feat(manifest): app manifest + decision crate (strict TOML subset, checked newtypes, capability catalog); docs/apps.md spec
This commit is contained in:
149
docs/apps.md
Normal file
149
docs/apps.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Apps, manifests and the permission manager
|
||||
|
||||
How a program gets its authority. The kernel only knows capabilities; this
|
||||
is the userspace layer that decides who holds which, on the owner's behalf
|
||||
(see the vision's Capability Management and App Manifest notes).
|
||||
|
||||
## Apps
|
||||
|
||||
Every program the owner runs is an **app**: a manifest plus a binary.
|
||||
An app is identified by its **id** (`kos.snake`), never by a file name or a
|
||||
process id. The id is what decisions, the app's data folder and (later) its
|
||||
encryption keys hang off.
|
||||
|
||||
System servers (log, display, terminal, storage, …) are not apps: init
|
||||
starts them from its own table, with exactly the capabilities listed there.
|
||||
|
||||
## Where authority comes from
|
||||
|
||||
An app's capabilities have exactly two sources:
|
||||
|
||||
1. **Its manifest and the owner's consent.** Services the app declared
|
||||
(`display`, `storage`, …), granted only by the permission manager, only
|
||||
as far as the owner approved.
|
||||
2. **Its launcher.** Whoever starts an app may pass along what it already
|
||||
holds: the terminal as its output, a read-only grant to the current
|
||||
folder, a file the owner picked. A launcher can only give what it has.
|
||||
|
||||
`cat` declares nothing and gets only what the shell passes it. Snake
|
||||
declares `display`. Nothing gets anything "just in case".
|
||||
|
||||
## Disk layout
|
||||
|
||||
```
|
||||
/home/ the owner's files; the shell's `/`
|
||||
/apps/<id>/ an installed app: manifest.toml and its binary
|
||||
/data/<id>/ the app's private folder (its `storage` capability)
|
||||
/system/permissions/ the owner's decisions, one file per app id
|
||||
/bin/ system servers and the shell (init starts these)
|
||||
```
|
||||
|
||||
Only the permission manager reaches `/apps`, `/data` and `/system`. The
|
||||
shell's storage grant is `/home`, so neither it nor anything it runs can
|
||||
read another app's data or rewrite a decision.
|
||||
|
||||
## Manifest
|
||||
|
||||
`/apps/<id>/manifest.toml`, a strict subset of TOML:
|
||||
|
||||
```toml
|
||||
[app]
|
||||
id = "kos.snake" # required: lowercase reverse-DNS
|
||||
name = "Snake" # required
|
||||
version = "1.0.0" # required: major.minor.patch
|
||||
binary = "snake" # required: a file in the app's folder
|
||||
command = "snake" # optional: what the shell runs it as (default: binary)
|
||||
author = "K-OS" # optional
|
||||
icon = "icon.png" # optional: a file in the app's folder
|
||||
|
||||
[capabilities]
|
||||
required = ["display"] # cannot work without; denied = not installed
|
||||
optional = [] # works without; the owner decides each
|
||||
|
||||
[appearance]
|
||||
accent = "#e91e63" # optional: checked for format here, contrast by K-UI
|
||||
```
|
||||
|
||||
The subset: `[table]` headers, `key = "string"`, `key = ["a", "b"]` on one
|
||||
line, `#` comments. Strings hold printable ASCII without `"` or `\`. It is
|
||||
strict: an unknown table, key or capability name, a duplicate, or a
|
||||
missing required key rejects the manifest. Nothing is ignored silently.
|
||||
|
||||
Capability names come from the server catalog (`display`, `storage`,
|
||||
`network`, `telephony`, …). A name is valid even if this device lacks the
|
||||
server; a required one it lacks means the app cannot be installed here, an
|
||||
optional one is simply not granted.
|
||||
|
||||
| Capability | What the app gets |
|
||||
|---|---|
|
||||
| `display` | the display server: the screen, and the keyboard while in focus |
|
||||
| `storage` | `/data/<id>/`, read-write (its own folder only) |
|
||||
|
||||
## Decisions
|
||||
|
||||
`/system/permissions/<id>`, the same TOML subset:
|
||||
|
||||
```toml
|
||||
[decision]
|
||||
granted = ["display"]
|
||||
```
|
||||
|
||||
Written by the permission manager when the owner decides; apps on the
|
||||
system image ship with their decisions already recorded (pre-approved by
|
||||
whoever built the image). Changing one later is the owner's call, through
|
||||
the permission manager.
|
||||
|
||||
## Permission manager
|
||||
|
||||
A system server with no hardware. Its logic (checking manifests, deciding
|
||||
what an app gets, keeping decisions) is pure and generic over two ports:
|
||||
|
||||
- `Consent`: asks the owner. Today: a yes/no prompt on the terminal.
|
||||
- `Store`: reads manifests and decisions, writes decisions, installs files.
|
||||
Today: the storage server.
|
||||
|
||||
Both have mocks; the logic is tested on the host with `cargo test`.
|
||||
|
||||
### Install
|
||||
|
||||
From a folder the launcher passes as a read-only grant:
|
||||
|
||||
1. Read and check its `manifest.toml`; refuse if the id is installed.
|
||||
2. Required capabilities this device lacks: refuse.
|
||||
3. Ask the owner about the required ones together (no = not installed),
|
||||
then each optional one.
|
||||
4. Copy the binary and manifest to `/apps/<id>/`, create `/data/<id>/` if
|
||||
`storage` was granted, record the decision.
|
||||
|
||||
### Launch
|
||||
|
||||
The launcher names an app (by command or id), passes its arguments and the
|
||||
capabilities it wants to pass along (output, current folder). The
|
||||
permission manager:
|
||||
|
||||
1. Looks the app up and reads its decision.
|
||||
2. Gives init a read-only grant to the binary; init loads it from that
|
||||
grant into a new, unstarted process (init needs no path, and nothing is
|
||||
spawned that its requester could not read).
|
||||
3. Gives the process the granted services and what the launcher passed,
|
||||
writes its arguments page, starts it.
|
||||
4. Watches it die, then releases its storage grants.
|
||||
5. Replies with a handle (the process capability, call-only) the launcher
|
||||
can wait on and kill.
|
||||
|
||||
### Revoke
|
||||
|
||||
The owner withdraws a capability: the decision changes and the next launch
|
||||
gets less. A running app that held it is **stopped**: all its access dies
|
||||
at once. (Endpoint capabilities like `display` are copies the permission
|
||||
manager cannot take back from one app alone yet; once servers issue
|
||||
per-client badges, as storage does, revoking a single capability from a
|
||||
running app will not need to stop it.)
|
||||
|
||||
### Not yet
|
||||
|
||||
- A trusted path for the consent prompt: today an app could draw a fake
|
||||
prompt on the terminal (it cannot answer one; apps cannot type).
|
||||
- Suspicious-combination warnings, simple/granular consent, per-host or
|
||||
other server-side scopes.
|
||||
- Signing, updates, uninstall.
|
||||
Reference in New Issue
Block a user