@calimero-network/agent-skills 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -17
- package/SKILL.md +31 -28
- package/package.json +1 -1
- package/scripts/install.js +3 -3
- package/scripts/test.js +6 -15
- package/skills/calimero-abi-codegen/SKILL.md +121 -22
- package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
- package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
- package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
- package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
- package/skills/calimero-client-js/SKILL.md +126 -31
- package/skills/calimero-client-js/references/auth.md +18 -10
- package/skills/calimero-client-js/references/rpc-calls.md +15 -21
- package/skills/calimero-client-js/references/sso.md +9 -9
- package/skills/calimero-client-js/references/websocket-events.md +73 -92
- package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
- package/skills/calimero-client-js/rules/token-refresh.md +11 -11
- package/skills/calimero-client-py/SKILL.md +25 -13
- package/skills/calimero-client-py/references/api.md +41 -43
- package/skills/calimero-client-py/references/auth.md +7 -7
- package/skills/calimero-client-py/rules/async-usage.md +27 -31
- package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
- package/skills/calimero-core/SKILL.md +135 -0
- package/skills/calimero-core/references/architecture.md +101 -0
- package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
- package/skills/calimero-core/references/namespaces-groups.md +94 -0
- package/skills/calimero-core/references/storage-types.md +118 -0
- package/skills/calimero-core/references/websocket-events.md +142 -0
- package/skills/calimero-core/rules/context-is-not-app.md +35 -0
- package/skills/calimero-core/rules/crdt-types-only.md +55 -0
- package/skills/calimero-desktop/SKILL.md +24 -19
- package/skills/calimero-desktop/references/sso-integration.md +2 -2
- package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
- package/skills/calimero-merobox/SKILL.md +255 -28
- package/skills/calimero-merobox/references/ci-integration.md +3 -2
- package/skills/calimero-merobox/references/workflow-files.md +7 -5
- package/skills/calimero-merobox/rules/docker-required.md +7 -6
- package/skills/calimero-meroctl/SKILL.md +68 -0
- package/skills/calimero-meroctl/references/commands.md +177 -0
- package/skills/calimero-meroctl/references/scripting.md +80 -0
- package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
- package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
- package/skills/calimero-merod/SKILL.md +49 -0
- package/skills/calimero-merod/references/health-endpoints.md +90 -0
- package/skills/calimero-merod/references/init-flags.md +84 -0
- package/skills/calimero-merod/rules/init-before-run.md +40 -0
- package/skills/calimero-merod/rules/port-assignments.md +33 -0
- package/skills/calimero-node/SKILL.md +50 -39
- package/skills/calimero-node/references/context-lifecycle.md +34 -17
- package/skills/calimero-node/references/meroctl-commands.md +89 -99
- package/skills/calimero-node/rules/app-vs-context.md +4 -4
- package/skills/calimero-registry/SKILL.md +110 -31
- package/skills/calimero-registry/references/bundle-and-push.md +99 -34
- package/skills/calimero-registry/references/manifest-format.md +56 -35
- package/skills/calimero-registry/references/mero-sign.md +10 -9
- package/skills/calimero-registry/rules/key-security.md +3 -2
- package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
- package/skills/calimero-rust-sdk/SKILL.md +154 -44
- package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
- package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
- package/skills/calimero-rust-sdk/references/events.md +2 -1
- package/skills/calimero-rust-sdk/references/examples.md +81 -29
- package/skills/calimero-rust-sdk/references/migrations.md +123 -0
- package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
- package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
- package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
- package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
- package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
- package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
- package/skills/calimero-rust-sdk/rules/state-derives.md +9 -10
- package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
- package/skills/calimero-sdk-js/SKILL.md +34 -26
- package/skills/calimero-sdk-js/references/build-pipeline.md +6 -6
- package/skills/calimero-sdk-js/references/collections.md +11 -11
- package/skills/calimero-sdk-js/references/events.md +7 -3
- package/skills/calimero-sdk-js/rules/crdt-only-state.md +18 -18
- package/skills/calimero-sdk-js/rules/no-console-log.md +6 -6
- package/skills/calimero-sdk-js/rules/view-decorator.md +6 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Rule: node_name must be stable and unique per node
|
|
2
2
|
|
|
3
|
-
`node_name` is used to derive the token cache filename. Changing it between runs
|
|
4
|
-
|
|
3
|
+
`node_name` is used to derive the token cache filename. Changing it between runs means the client
|
|
4
|
+
can't find the cached tokens and will fail to authenticate.
|
|
5
5
|
|
|
6
6
|
## WRONG — dynamic or None:
|
|
7
7
|
|
|
@@ -29,11 +29,11 @@ connection = create_connection(api_url=url, node_name=node_name)
|
|
|
29
29
|
|
|
30
30
|
## Why
|
|
31
31
|
|
|
32
|
-
Token cache files are named `{sanitized_node_name}-{hash}.json` in `~/.merobox/auth_cache/`.
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
Token cache files are named `{sanitized_node_name}-{hash}.json` in `~/.merobox/auth_cache/`. If
|
|
33
|
+
`node_name` varies, a new cache file is created each run and existing tokens are never reused —
|
|
34
|
+
forcing re-authentication on every invocation.
|
|
35
35
|
|
|
36
36
|
## Also: unique per remote node
|
|
37
37
|
|
|
38
|
-
If you connect to multiple nodes in the same script, use a different `node_name` for each.
|
|
39
|
-
|
|
38
|
+
If you connect to multiple nodes in the same script, use a different `node_name` for each. Sharing a
|
|
39
|
+
`node_name` across two different nodes will mix up their token caches.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# calimero-core — Agent Instructions
|
|
2
|
+
|
|
3
|
+
You are helping a developer understand the **core Calimero runtime** — the mental model, protocols,
|
|
4
|
+
and primitives that every application, client library, and node operator needs to know.
|
|
5
|
+
|
|
6
|
+
> Use this skill alongside a language-specific skill (`calimero-rust-sdk`, `calimero-sdk-js`,
|
|
7
|
+
> `calimero-client-js`, etc.) to give the AI full-stack context.
|
|
8
|
+
|
|
9
|
+
## The three-layer model
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
┌─────────────────────────────────────────────────────┐
|
|
13
|
+
│ Application logic (WASM) │ calimero-rust-sdk / calimero-sdk-js
|
|
14
|
+
│ @State / #[app::state], CRDT collections │
|
|
15
|
+
└────────────────────────┬────────────────────────────┘
|
|
16
|
+
│ JSON-RPC calls + WebSocket events
|
|
17
|
+
┌────────────────────────▼────────────────────────────┐
|
|
18
|
+
│ merod node runtime │ calimero-merod
|
|
19
|
+
│ Hosts WASM, manages storage, exposes HTTP/WS API │
|
|
20
|
+
└────────────────────────┬────────────────────────────┘
|
|
21
|
+
│ meroctl CLI / HTTP clients / Python client
|
|
22
|
+
┌────────────────────────▼────────────────────────────┐
|
|
23
|
+
│ Clients & tooling │ calimero-client-js / calimero-client-py
|
|
24
|
+
│ mero-js, mero-react, calimero-client-py │ calimero-meroctl
|
|
25
|
+
└─────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Core concepts
|
|
29
|
+
|
|
30
|
+
### Application vs Context
|
|
31
|
+
|
|
32
|
+
| Concept | What it is | Analogy |
|
|
33
|
+
| --------------- | ------------------------------------------------------------------ | -------------------- |
|
|
34
|
+
| **Application** | WASM binary + manifest; the code | A class / template |
|
|
35
|
+
| **Context** | A running instance of an app; has isolated state, members, storage | An object / instance |
|
|
36
|
+
|
|
37
|
+
One application can power many independent contexts. State is never shared across contexts.
|
|
38
|
+
|
|
39
|
+
### Identity
|
|
40
|
+
|
|
41
|
+
An **identity** is an Ed25519 keypair. Each context member has an identity on the node. The
|
|
42
|
+
identity's public key is used:
|
|
43
|
+
|
|
44
|
+
- As `executorPublicKey` in RPC calls (who is calling)
|
|
45
|
+
- For signing mutations in the CRDT sync protocol
|
|
46
|
+
|
|
47
|
+
### Namespace and Group
|
|
48
|
+
|
|
49
|
+
| Concept | Role |
|
|
50
|
+
| ------------- | ------------------------------------------------------------------------------------------ |
|
|
51
|
+
| **Namespace** | A root trust anchor shared across nodes; created by one node, others join via invite token |
|
|
52
|
+
| **Group** | Sub-grouping within a namespace; contexts are joined via group membership |
|
|
53
|
+
|
|
54
|
+
Multi-node participation requires: create namespace → invite peer → peer joins namespace → peer
|
|
55
|
+
joins context via group.
|
|
56
|
+
|
|
57
|
+
## How calls work (JSON-RPC)
|
|
58
|
+
|
|
59
|
+
All application method calls go through the node's JSON-RPC endpoint:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
POST http://localhost:2428/api/v0/context/{contextId}/execute
|
|
63
|
+
Authorization: Bearer <access_token>
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
"method": "get_posts",
|
|
67
|
+
"argsJson": "{}",
|
|
68
|
+
"executorPublicKey": "<base58-pubkey>"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **Mutations** (methods taking `&mut self`) change shared CRDT state; changes are synced to all
|
|
73
|
+
context members automatically.
|
|
74
|
+
- **Views** (methods taking `&self`, annotated `@View` or returning read-only) do NOT persist state.
|
|
75
|
+
- There is no separate "query" endpoint — mutations and views use the same call path. The `--view`
|
|
76
|
+
flag in `meroctl call` tells the client to skip state persistence.
|
|
77
|
+
|
|
78
|
+
## How events work (WebSocket)
|
|
79
|
+
|
|
80
|
+
Applications emit events with `app::emit!()` (Rust) or `env.emit()` (JS). Clients subscribe via
|
|
81
|
+
WebSocket to receive them in real time.
|
|
82
|
+
|
|
83
|
+
Two event types the node sends to subscribers:
|
|
84
|
+
|
|
85
|
+
| Type | When | Payload |
|
|
86
|
+
| ---------------- | ----------------------------- | ------------------------------ |
|
|
87
|
+
| `ExecutionEvent` | App called `app::emit!()` | `{ events: [{ kind, data }] }` |
|
|
88
|
+
| `StateMutation` | A member mutated shared state | `{ newRoot: string }` |
|
|
89
|
+
|
|
90
|
+
WebSocket endpoint: `ws://localhost:2428/ws`
|
|
91
|
+
|
|
92
|
+
## CRDT storage types
|
|
93
|
+
|
|
94
|
+
All shared application state uses conflict-free replicated data types from
|
|
95
|
+
`calimero_storage::collections`. Plain `HashMap`, `Vec`, or `HashSet` must never be used for shared
|
|
96
|
+
state.
|
|
97
|
+
|
|
98
|
+
| Type | Use for |
|
|
99
|
+
| --------------------------- | --------------------------------------- |
|
|
100
|
+
| `UnorderedMap<K, V>` | Key-value store (most common) |
|
|
101
|
+
| `Vector<T>` | Append-only ordered log |
|
|
102
|
+
| `UnorderedSet<T>` | Unique value set |
|
|
103
|
+
| `LwwRegister<T>` | Single last-write-wins scalar |
|
|
104
|
+
| `Counter` / `Counter<true>` | Grow-only / PN counter |
|
|
105
|
+
| `FrozenStorage<T>` | Immutable content-addressed blobs |
|
|
106
|
+
| `UserStorage<T>` | Per-member private storage (not synced) |
|
|
107
|
+
| `ReplicatedGrowableArray` | Collaborative text / ordered sequence |
|
|
108
|
+
|
|
109
|
+
## Authentication
|
|
110
|
+
|
|
111
|
+
The node uses short-lived JWT access tokens + long-lived refresh tokens:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Get tokens
|
|
115
|
+
POST /api/v0/identity/login
|
|
116
|
+
{ "username": "admin", "password": "..." }
|
|
117
|
+
→ { "accessToken": "...", "refreshToken": "..." }
|
|
118
|
+
|
|
119
|
+
# Refresh
|
|
120
|
+
POST /api/v0/identity/refresh
|
|
121
|
+
{ "refreshToken": "..." }
|
|
122
|
+
→ { "accessToken": "..." }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Tokens are passed as `Authorization: Bearer <accessToken>` on all API calls.
|
|
126
|
+
|
|
127
|
+
## References
|
|
128
|
+
|
|
129
|
+
See `references/` for:
|
|
130
|
+
|
|
131
|
+
- Full JSON-RPC protocol and endpoint list
|
|
132
|
+
- WebSocket event schemas with decoding examples
|
|
133
|
+
- CRDT storage type guide
|
|
134
|
+
- Namespace and group model detail
|
|
135
|
+
- Architecture overview
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Calimero Architecture
|
|
2
|
+
|
|
3
|
+
## What merod is
|
|
4
|
+
|
|
5
|
+
`merod` is the Calimero node daemon. It:
|
|
6
|
+
|
|
7
|
+
- Hosts and executes WASM applications inside isolated contexts
|
|
8
|
+
- Manages persistent CRDT storage per context
|
|
9
|
+
- Exposes a JSON-RPC + WebSocket API on a configurable HTTP port
|
|
10
|
+
- Participates in a P2P network on a separate swarm port for state sync
|
|
11
|
+
- Issues and validates JWT tokens for client auth
|
|
12
|
+
|
|
13
|
+
One `merod` process = one node. Multiple nodes can be run on the same machine with different
|
|
14
|
+
`--node` names and different ports.
|
|
15
|
+
|
|
16
|
+
## What a Context is
|
|
17
|
+
|
|
18
|
+
A **context** is a sandboxed instance of a WASM application with:
|
|
19
|
+
|
|
20
|
+
- Its own isolated CRDT state (not shared with other contexts, even of the same app)
|
|
21
|
+
- Its own set of **members** (identities that can call methods and receive events)
|
|
22
|
+
- Its own blob storage
|
|
23
|
+
- A unique `context-id` (hex string)
|
|
24
|
+
|
|
25
|
+
When a context is created, the app's `init()` / `@Init` method is called once to seed the initial
|
|
26
|
+
state.
|
|
27
|
+
|
|
28
|
+
## What an Application is
|
|
29
|
+
|
|
30
|
+
An **application** is a compiled WASM binary + optional manifest. It has:
|
|
31
|
+
|
|
32
|
+
- A unique `application-id` assigned at install time
|
|
33
|
+
- No state of its own — state lives in contexts that instantiate it
|
|
34
|
+
- One app can power many independent contexts
|
|
35
|
+
|
|
36
|
+
Installing an app does NOT create a context. These are always two separate steps.
|
|
37
|
+
|
|
38
|
+
## What an Identity is
|
|
39
|
+
|
|
40
|
+
An **identity** is an Ed25519 keypair managed by the node:
|
|
41
|
+
|
|
42
|
+
- `identityId` — the base58-encoded public key, used as `executorPublicKey` in RPC calls
|
|
43
|
+
- Identities are created per node (`meroctl identity create`)
|
|
44
|
+
- An identity participates in a context as a member
|
|
45
|
+
|
|
46
|
+
## Call path for an app method
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
Client
|
|
50
|
+
│ POST /api/v0/context/{contextId}/execute
|
|
51
|
+
│ { method, argsJson, executorPublicKey }
|
|
52
|
+
▼
|
|
53
|
+
merod HTTP handler
|
|
54
|
+
│ validates JWT, resolves context
|
|
55
|
+
▼
|
|
56
|
+
WASM executor
|
|
57
|
+
│ deserializes state from CRDT storage
|
|
58
|
+
│ calls the method
|
|
59
|
+
│ serializes mutations back to storage
|
|
60
|
+
▼
|
|
61
|
+
CRDT sync engine
|
|
62
|
+
│ broadcasts mutation to all context members (P2P)
|
|
63
|
+
▼
|
|
64
|
+
WebSocket subscribers
|
|
65
|
+
receive ExecutionEvent / StateMutation
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Sync model
|
|
69
|
+
|
|
70
|
+
State sync is automatic and happens over the P2P network:
|
|
71
|
+
|
|
72
|
+
- All context members receive all mutations from all other members
|
|
73
|
+
- Conflicts are resolved deterministically by the CRDT merge rules
|
|
74
|
+
- Sync works offline — mutations queue and merge on reconnect
|
|
75
|
+
- Manual sync trigger: `meroctl context sync <context-id>`
|
|
76
|
+
|
|
77
|
+
## Multi-node participation
|
|
78
|
+
|
|
79
|
+
Nodes join contexts through the namespace + group model:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Node A creates a namespace (trust root)
|
|
83
|
+
→ Node A creates a context
|
|
84
|
+
→ Node A generates namespace invite token
|
|
85
|
+
→ Node B accepts the invite (joins namespace)
|
|
86
|
+
→ Node B joins the context via group membership
|
|
87
|
+
→ Both nodes now sync CRDT state for that context
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Once joined, state sync is fully automatic. Node B receives all future mutations from Node A (and
|
|
91
|
+
vice versa) without any further configuration.
|
|
92
|
+
|
|
93
|
+
## Port assignment
|
|
94
|
+
|
|
95
|
+
| Port | Purpose |
|
|
96
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
97
|
+
| `--server-port` (default 2428) | HTTP/WS API — clients and meroctl connect here |
|
|
98
|
+
| `--swarm-port` (default 2528) | P2P swarm — inter-node state sync, invite protocol |
|
|
99
|
+
|
|
100
|
+
These ports must be accessible to meroctl and app clients (server port), and to other nodes for sync
|
|
101
|
+
(swarm port).
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# JSON-RPC Protocol
|
|
2
|
+
|
|
3
|
+
The Calimero node exposes its application and admin APIs over HTTP/REST at
|
|
4
|
+
`http://localhost:2428/api/v0/` by default.
|
|
5
|
+
|
|
6
|
+
## Base URL
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
http://<node-host>:<server-port>/api/v0
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Default: `http://localhost:2428/api/v0`
|
|
13
|
+
|
|
14
|
+
All endpoints require `Authorization: Bearer <accessToken>` except `/identity/login`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Authentication endpoints
|
|
19
|
+
|
|
20
|
+
### Login
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
POST /api/v0/identity/login
|
|
24
|
+
|
|
25
|
+
Body: { "username": "admin", "password": "..." }
|
|
26
|
+
|
|
27
|
+
Response: {
|
|
28
|
+
"accessToken": "eyJ...",
|
|
29
|
+
"refreshToken": "eyJ..."
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Refresh
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
POST /api/v0/identity/refresh
|
|
37
|
+
|
|
38
|
+
Body: { "refreshToken": "eyJ..." }
|
|
39
|
+
|
|
40
|
+
Response: { "accessToken": "eyJ..." }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Application method execution
|
|
46
|
+
|
|
47
|
+
This is how frontends and clients invoke WASM app logic.
|
|
48
|
+
|
|
49
|
+
### Execute a method (mutation or view)
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
POST /api/v0/context/{contextId}/execute
|
|
53
|
+
|
|
54
|
+
Headers:
|
|
55
|
+
Authorization: Bearer <accessToken>
|
|
56
|
+
|
|
57
|
+
Body: {
|
|
58
|
+
"method": "method_name",
|
|
59
|
+
"argsJson": "{\"key\":\"hello\"}",
|
|
60
|
+
"executorPublicKey": "<base58-identity-pubkey>"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Response (success): {
|
|
64
|
+
"output": <json-value> // method return value, null for void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Response (error): {
|
|
68
|
+
"error": {
|
|
69
|
+
"cause": {
|
|
70
|
+
"info": { "message": "..." }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `argsJson` is a **JSON string** (double-encoded) — not an inline object.
|
|
77
|
+
- `executorPublicKey` is the base58 public key of the identity making the call.
|
|
78
|
+
- Mutations and views use the same endpoint; views skip state persistence. The `mero-js` /
|
|
79
|
+
`mero-react` SDK handles this transparently.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Context management endpoints
|
|
84
|
+
|
|
85
|
+
### List contexts
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
GET /api/v0/contexts
|
|
89
|
+
|
|
90
|
+
Response: [
|
|
91
|
+
{ "id": "<context-id>", "applicationId": "<app-id>", ... },
|
|
92
|
+
...
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Get context details
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
GET /api/v0/contexts/{contextId}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Create context
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
POST /api/v0/contexts
|
|
106
|
+
|
|
107
|
+
Body: { "applicationId": "<app-id>" }
|
|
108
|
+
|
|
109
|
+
Response: { "id": "<context-id>" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Delete context
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
DELETE /api/v0/contexts/{contextId}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Application management endpoints
|
|
121
|
+
|
|
122
|
+
### List installed applications
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
GET /api/v0/applications
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Install application from local file
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
POST /api/v0/applications
|
|
132
|
+
Content-Type: multipart/form-data
|
|
133
|
+
|
|
134
|
+
file: <wasm-binary>
|
|
135
|
+
|
|
136
|
+
Response: { "applicationId": "<id>" }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Get application details
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
GET /api/v0/applications/{applicationId}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Identity endpoints
|
|
148
|
+
|
|
149
|
+
### List identities
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
GET /api/v0/identities
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Create identity
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
POST /api/v0/identities
|
|
159
|
+
|
|
160
|
+
Response: { "publicKey": "<base58>", "privateKey": "<base58>" }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## WebSocket endpoint
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
ws://localhost:2428/ws
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
After connecting, send a subscribe message:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{ "action": "subscribe", "contextIds": ["<context-id>"] }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The node will push `ExecutionEvent` and `StateMutation` messages. See `websocket-events.md` for the
|
|
178
|
+
full schema.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Error codes
|
|
183
|
+
|
|
184
|
+
| HTTP status | Meaning |
|
|
185
|
+
| ----------- | ------------------------------------------------------------- |
|
|
186
|
+
| `400` | Bad request — malformed JSON, missing fields |
|
|
187
|
+
| `401` | Unauthorized — missing or expired access token |
|
|
188
|
+
| `403` | Forbidden — identity not a context member |
|
|
189
|
+
| `404` | Not found — unknown context or application ID |
|
|
190
|
+
| `500` | Internal server error — WASM execution panic or storage error |
|
|
191
|
+
|
|
192
|
+
On `401`, refresh the access token and retry.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Namespaces and Groups
|
|
2
|
+
|
|
3
|
+
Namespaces and groups are the multi-node participation primitives in Calimero. They control which
|
|
4
|
+
nodes can join which contexts and how trust is established between nodes.
|
|
5
|
+
|
|
6
|
+
## Concepts
|
|
7
|
+
|
|
8
|
+
| Concept | Role |
|
|
9
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
10
|
+
| **Namespace** | A root trust anchor created by one node. Other nodes join by accepting an invite token generated by the creator. |
|
|
11
|
+
| **Group** | A sub-grouping within a namespace. Contexts are joined via group membership, not directly. |
|
|
12
|
+
|
|
13
|
+
A node must be in the correct namespace/group before it can participate in a context.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Full multi-node setup
|
|
18
|
+
|
|
19
|
+
### Node A: Create and invite
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Create a namespace (trust root)
|
|
23
|
+
meroctl --node node1 namespace create
|
|
24
|
+
# → <namespace-id>
|
|
25
|
+
|
|
26
|
+
# 2. Create a context from an installed app
|
|
27
|
+
meroctl --node node1 context create --application-id <app-id>
|
|
28
|
+
# → <context-id>
|
|
29
|
+
|
|
30
|
+
# 3. Generate an invite for Node B
|
|
31
|
+
meroctl --node node1 namespace invite <namespace-id>
|
|
32
|
+
# → prints invitation JSON payload — share this with Node B out-of-band
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Node B: Accept and join
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 4. Accept the namespace invite
|
|
39
|
+
meroctl --node node2 namespace join <namespace-id> '<invitation-json>'
|
|
40
|
+
|
|
41
|
+
# 5. Join the context via group membership
|
|
42
|
+
meroctl --node node2 group join-context <context-id>
|
|
43
|
+
# Node B is now a context member and will sync CRDT state from Node A
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
After step 5, state sync is fully automatic. Both nodes send and receive mutations through the P2P
|
|
47
|
+
swarm.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Namespace commands
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Create a new namespace
|
|
55
|
+
meroctl --node node1 namespace create
|
|
56
|
+
|
|
57
|
+
# List namespaces on this node
|
|
58
|
+
meroctl --node node1 namespace ls
|
|
59
|
+
|
|
60
|
+
# Generate an invite token for a namespace
|
|
61
|
+
meroctl --node node1 namespace invite <namespace-id>
|
|
62
|
+
|
|
63
|
+
# Join a namespace (Node B)
|
|
64
|
+
meroctl --node node2 namespace join <namespace-id> '<invitation-json>'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Group commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# List groups
|
|
73
|
+
meroctl --node node1 group ls
|
|
74
|
+
|
|
75
|
+
# Join a context via group
|
|
76
|
+
meroctl --node node2 group join-context <context-id>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## What happens after a node joins
|
|
82
|
+
|
|
83
|
+
1. The new member receives the full CRDT state from existing members.
|
|
84
|
+
2. Future mutations from any member are broadcast to all other members.
|
|
85
|
+
3. Offline mutations are queued and merged on reconnect — no data is lost.
|
|
86
|
+
4. The new member's identity can execute methods and its mutations are accepted by other members.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Single-node vs multi-node
|
|
91
|
+
|
|
92
|
+
For local development on a single node you do not need namespaces or groups — create a context
|
|
93
|
+
directly and call methods. Namespaces/groups are only required when two or more `merod` instances
|
|
94
|
+
need to share a context.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# CRDT Storage Types
|
|
2
|
+
|
|
3
|
+
All shared application state must use conflict-free replicated data types (CRDTs). These types
|
|
4
|
+
handle concurrent writes from multiple context members automatically.
|
|
5
|
+
|
|
6
|
+
## Why CRDTs
|
|
7
|
+
|
|
8
|
+
When two nodes both mutate state while offline and then reconnect, the CRDT engine merges both sets
|
|
9
|
+
of changes deterministically — no manual conflict resolution needed. Standard `HashMap`, `Vec`, or
|
|
10
|
+
`HashSet` do not have this property and must never be used for state that is shared across context
|
|
11
|
+
members.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Type reference
|
|
16
|
+
|
|
17
|
+
| Type | Use for | Key rule |
|
|
18
|
+
| ------------------------- | ------------------------------------- | ------------------------------------------------- |
|
|
19
|
+
| `UnorderedMap<K, V>` | Key-value store | Wrap values in `LwwRegister<V>` for scalar values |
|
|
20
|
+
| `Vector<T>` | Append-only ordered log | Cannot remove items |
|
|
21
|
+
| `UnorderedSet<T>` | Unique value collection | Grow-only by default |
|
|
22
|
+
| `LwwRegister<T>` | Single mutable scalar | Last write wins — safe for simple values |
|
|
23
|
+
| `Counter` | Grow-only integer counter | `.increment()`, `.value()` |
|
|
24
|
+
| `Counter<true>` | PN counter (supports decrement) | `.increment()`, `.decrement()`, `.value()` |
|
|
25
|
+
| `FrozenStorage<T>` | Immutable content-addressed entries | Write once; identified by hash |
|
|
26
|
+
| `UserStorage<T>` | Per-member private storage | NOT synced to other members |
|
|
27
|
+
| `ReplicatedGrowableArray` | Collaborative text / ordered sequence | For collaborative editing |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Rust import
|
|
32
|
+
|
|
33
|
+
```rust
|
|
34
|
+
use calimero_storage::collections::{
|
|
35
|
+
Counter, FrozenStorage, LwwRegister, ReplicatedGrowableArray,
|
|
36
|
+
UnorderedMap, UnorderedSet, UserStorage, Vector,
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **Critical:** Do NOT use `calimero_sdk::state::*` — that path no longer exists.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Common patterns
|
|
45
|
+
|
|
46
|
+
### Key-value store
|
|
47
|
+
|
|
48
|
+
```rust
|
|
49
|
+
items: UnorderedMap<String, LwwRegister<String>>,
|
|
50
|
+
|
|
51
|
+
// Write
|
|
52
|
+
self.items.insert(key, LwwRegister::new(value))?;
|
|
53
|
+
|
|
54
|
+
// In-place update (no clone needed)
|
|
55
|
+
if let Some(mut guard) = self.items.get_mut(&key)? {
|
|
56
|
+
guard.set(new_value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Read
|
|
60
|
+
let val = self.items.get(&key)?.map(|v| v.get().clone());
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Append-only log
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
log: Vector<String>,
|
|
67
|
+
|
|
68
|
+
self.log.push(entry)?;
|
|
69
|
+
let all: Vec<String> = self.log.iter()?.collect();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Counter
|
|
73
|
+
|
|
74
|
+
```rust
|
|
75
|
+
counter: Counter,
|
|
76
|
+
|
|
77
|
+
self.counter.increment()?;
|
|
78
|
+
let n = self.counter.value()?;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Per-member private data (not synced)
|
|
82
|
+
|
|
83
|
+
```rust
|
|
84
|
+
notes: UserStorage<String>,
|
|
85
|
+
|
|
86
|
+
self.notes.set("my private note".to_string())?;
|
|
87
|
+
let note = self.notes.get()?;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## TypeScript (SDK JS) equivalents
|
|
93
|
+
|
|
94
|
+
The JS SDK provides equivalent CRDT types:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { UnorderedMap, Vector, Counter } from '@calimero-network/calimero-sdk-js/collections';
|
|
98
|
+
|
|
99
|
+
@State
|
|
100
|
+
class AppState {
|
|
101
|
+
items: UnorderedMap<string, string> = new UnorderedMap();
|
|
102
|
+
log: Vector<string> = new Vector();
|
|
103
|
+
counter: Counter = new Counter();
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
JS state fields must also be CRDT types — no plain `Map`, `Set`, or `Array`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Rules
|
|
112
|
+
|
|
113
|
+
1. **Never use `std::collections` for shared state.** `HashMap`, `BTreeMap`, `Vec`, `HashSet` are
|
|
114
|
+
not CRDTs and will cause data loss on concurrent writes.
|
|
115
|
+
2. **All collection operations are fallible in Rust.** Always use `?` to propagate errors.
|
|
116
|
+
3. **`FrozenStorage` is write-once.** Use it for immutable content (images, blobs) that are
|
|
117
|
+
identified by hash.
|
|
118
|
+
4. **`UserStorage` is per-member.** Only the local member can read/write it — it is never synced.
|