actr-protocol 0.4.10

Unified protocol, types, and URI parsing for Actor-RTC framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
syntax = "proto2";

package actr;

import "google/protobuf/timestamp.proto";

// ===========================================================================
// Basic Components
//   Realm (Security Realm)
//   ActrType (manufacturer + name + version)
//   ActrId (Realm + serial number + type)
//   ServiceSpec (fingerprint + proto bundle + tags)
//   ActrNode (ActrId + ServiceSpec)
// ===========================================================================
message Realm {
  required uint32 realm_id = 1;
}

message ActrType {
  required string manufacturer = 1;
  required string name = 2;
  // Version string (e.g., "1.0.0", "v2", "stable"). Not required to follow
  // semantic versioning, but must be present.
  required string version = 3;
}

message ActrId {
  required Realm realm = 1;
  required uint64 serial_number = 2;
  required ActrType type = 3;
}

message ServiceSpec {
  required string name = 1;
  optional string description = 2;
  required string fingerprint = 3; // Deterministic hash combining all proto semantic fingerprints
  repeated Protobuf protobufs = 4;
  optional int64 published_at = 5; // Publication timestamp (Unix epoch seconds)
  repeated string tags = 6; // Tags like "latest", "stable"

  message Protobuf {
    required string package = 1; // Package name (e.g., "user.v1", "acme.payment.v2")
    required string content = 2; // Merged and normalized content for this package
    required string fingerprint = 3; // Semantic fingerprint of merged package content
  }
}

message ActrNode {
  required ActrId actr_id = 1;
  optional ServiceSpec service_spec = 2;
}

// Geographic location for load balancing
message ServiceLocation {
  optional string region = 1;      // Region identifier (e.g., "us-west", "cn-beijing")
  optional double latitude = 2;    // Latitude in decimal degrees (-90 to +90)
  optional double longitude = 3;   // Longitude in decimal degrees (-180 to +180)
}

// ============================================================================
// AId Credential
// ============================================================================

// Actor identity claims, encoded as plaintext protobuf and signed by AIS with Ed25519.
message IdentityClaims {
  required uint32 realm_id   = 1;
  required string actor_id   = 2;
  required uint64 expires_at = 3;
}

// Time-limited HMAC TURN credential in the format compatible with `coturn --use-auth-secret`.
message TurnCredential {
  required string username   = 1; // "<expires_at>:<actor_id>"
  required string password   = 2; // base64(HMAC-SHA1(turn_secret, username))
  required uint64 expires_at = 3;
}

// Credential structure for ActrId authentication
message AIdCredential {
  required uint32 key_id    = 1;
  required bytes  claims    = 2; // `proto_encode(IdentityClaims)` in plaintext
  required bytes  signature = 3; // Ed25519 signature, 64 bytes
}

// ============================================================================
// Access Control (type-to-type permissions)
// =========================================================================

// A single ACL rule: who (from_type @ source_realm) -> permission
message AclRule {
  enum Permission { DENY = 0; ALLOW = 1; }

  required Permission permission = 1;
  required ActrType   from_type  = 2;

  // Source realm selector. "self" in config is resolved to realm_id by actor
  // before registration; "*" in config maps to any_realm = true.
  oneof source_realm {
    bool   any_realm = 3; // wildcard: matches any realm
    uint32 realm_id  = 4; // specific realm
  }
}

// Access Control List.
// Evaluation: any matching DENY overrides all ALLOWs.
// Default policy: deny if no rule matches.
message Acl {
  repeated AclRule rules = 1;
}

// =========================================================================
// Actr States
//   LifecycleState: ActrNode Lifecycle State
//   ServiceAvailabilityState: ActrNode Availability State
//   ServiceDependencyState: ActrNode Service Dependency State
// =========================================================================

enum LifecycleState {
  HALTED = 0;
  INITIALIZING = 1;
  CONSTRUCTING = 2;
  RUNNING = 3;
  DESTRUCTING = 4;
  CRASHING = 5;
}

enum ServiceAvailabilityState {
  FULL = 0;
  DEGRADED = 1;
  OVERLOADED = 2;
  UNAVAILABLE = 3;
}

enum ServiceDependencyState {
  HEALTHY = 0;
  WARNING = 1;
  BROKEN = 2;
}

// ============================================================================
// Common Error (copied from actr/common.proto)
// ============================================================================

message ErrorResponse {
  required uint32 code = 1;
  required string message = 2;
}

// =========================================================================
// Register
// =========================================================================
enum RegisterAuthMode {
  REGISTER_AUTH_MODE_UNSPECIFIED = 0;
  // Package-backed registration authenticated by MFR package lookup,
  // manifest signature verification, or package renewal credentials.
  REGISTER_AUTH_MODE_PACKAGE = 1;
  // Source-linked workload registration authenticated by realm authorization.
  REGISTER_AUTH_MODE_LINKED = 2;
}

// Allocate a new ActrId and register node metadata and API info.
message RegisterRequest {
  required ActrType actr_type = 1;
  required Realm realm = 2;

  // API / contract metadata
  optional ServiceSpec service_spec = 3;
  optional Acl acl = 4;
  // Compact service reference: "ServiceName:fingerprint".
  // Present when strict proto fingerprint matching is required.
  optional string service = 5;
  // WebSocket direct-connect address (e.g., "ws://192.168.1.10:9100").
  // Set when the registering node has a WebSocket server listening for inbound
  // direct connections. The signaling server stores this and returns it to
  // clients via RouteCandidatesResponse so they can connect without a relay.
  optional string ws_address = 6;

  // Raw manifest bytes (original `actr.toml` from the `.actr` package) for MFR signature verification.
  optional bytes manifest_raw = 10;
  // Ed25519 signature over the manifest contents produced by the MFR, 64 bytes.
  optional bytes mfr_signature = 11;
  // Target platform (e.g. "wasm32-wasip1", "x86_64-unknown-linux-gnu").
  // Extracted from manifest binary.target for AIS lookup path verification.
  optional string target = 13;
  // Explicit authentication source. Omitted/UNSPECIFIED is treated as PACKAGE
  // for backward compatibility with older package clients.
  optional RegisterAuthMode auth_mode = 14;
  // Removed in breaking cleanup: legacy PSK renewal token.
  reserved 12;
  reserved "psk_token";

  // Ed25519 signature over the ACTR-MANUFACTURER-REGISTER-V1 payload.
  // A package launcher produces this fresh proof with the MFR key that signed
  // manifest_raw, binding the running package to its manufacturer.
  // Required only for unpublished Package Path 2 registrations.
  optional bytes manufacturer_auth_signature = 15;
  // Unix seconds included in the manufacturer signature payload.
  // AIS accepts only a short time window to prevent long-lived replay.
  optional uint64 manufacturer_auth_signed_at = 16;
  // Random nonce included in the manufacturer signature payload.
  // AIS atomically consumes it to prevent replay inside the time window.
  optional bytes manufacturer_auth_nonce = 17;
}

message RegisterResponse {
  message RegisterOk {
    // Allocated identity and credentials
    required ActrId        actr_id                          = 1;
    required AIdCredential credential                       = 2;
    required TurnCredential turn_credential                 = 3;
    optional google.protobuf.Timestamp credential_expires_at = 4;
    // Operational advice: heartbeat interval for subsequent Signaling Server connection
    required uint32        signaling_heartbeat_interval_secs = 5;
    required bytes         signing_pubkey                   = 6; // AIS Ed25519 verifying key,32 bytes
    required uint32        signing_key_id                   = 7;
    // Bearer token bound to actr_id and used by POST /ais/renew.
    optional bytes         renewal_token                    = 12;
    // Renewal token expiration timestamp.
    optional google.protobuf.Timestamp renewal_token_expires_at = 13;

    // Removed in breaking cleanup: legacy PSK fields.
    reserved 10, 11;
    reserved "psk", "psk_expires_at";
  }
  oneof result {
    RegisterOk success = 1;
    ErrorResponse error = 2;
  }
}

// Renew credentials for an existing actor identity.
message RenewCredentialRequest {
  required ActrId actr_id = 1;
  required bytes renewal_token = 2;
}

message RenewCredentialResponse {
  oneof result {
    RegisterResponse.RegisterOk success = 1;
    ErrorResponse error = 2;
  }
}

// ============================================================================
// Unregister
// ============================================================================

message UnregisterRequest {
  required ActrId actr_id = 1;
  optional string reason = 2;
}

message UnregisterResponse {
  message UnregisterOk {}
  oneof result {
    UnregisterOk success = 1;
    ErrorResponse error = 2;
  }
}

// =========================================================================
// Heartbeat
// =========================================================================
// Periodic liveness and load indicators.
// NOTE: Currently measures Node-to-Signaling latency only.
// For P2P latency-based routing, consider adding:
//   - optional uint64 signaling_rtt_ms = 4;  // Node-to-Signaling RTT
//   - LatencyReport message for P2P measurements
//     See RouteCandidatesRequest.LOWEST_LATENCY for design discussion.
message Ping {
  required ServiceAvailabilityState availability = 1;
  required float power_reserve = 2;
  required float mailbox_backlog = 3;

  // Sticky session support: list of client IDs that must route to this instance.
  // Used by LoadBalancer's CLIENT_AFFINITY factor for session persistence.
  // Actor instances report their currently connected sticky clients.
  repeated string sticky_client_ids = 4;
}

message Pong {
  required uint64 seq = 1;
  optional uint32 suggest_interval_secs = 2; // advisory
  optional CredentialWarning credential_warning = 3; // Credential warning, for example when a key is in its tolerance period.
}

// =========================================================================
// Load-balancing: find best route candidates
// =========================================================================
// Request route candidates for a target actor type with optional selection policy.
message RouteCandidatesRequest {
  message NodeSelectionCriteria {
    // Ranking factors for candidate ordering, applied in specified order.
    // Same ActrType implies protocol compatibility; EXACT_MATCH_FIRST further
    // prefers candidates whose proto fingerprint matches the requester's exactly.
    enum NodeRankingFactor {
      MAXIMUM_POWER_RESERVE = 0;
      MINIMUM_MAILBOX_BACKLOG = 1;
      EXACT_MATCH_FIRST = 2; // exact fingerprint match ranked above compatible
      NEAREST = 3;
      CLIENT_AFFINITY = 4;
    }

    required uint32 candidate_count = 1;
    repeated NodeRankingFactor ranking_factors = 2;
    optional ServiceDependencyState minimal_dependency_requirement = 3;
    optional ServiceAvailabilityState minimal_health_requirement = 4;
  }
  required ActrType target_type = 1;
  optional NodeSelectionCriteria criteria = 2;
  optional ServiceLocation client_location = 3;  // Client's geographic location for NEAREST ranking
  // Client's service fingerprint; used by EXACT_MATCH_FIRST ranking factor to
  // prioritise candidates whose fingerprint matches exactly.
  required string client_fingerprint = 4;
}

// WebSocket address entry for a route candidate
message WsAddressEntry {
  required ActrId candidate_id = 1;
  optional string ws_address = 2;
}

message RouteCandidatesResponse {
  message RouteCandidatesOk {
    repeated ActrId candidates = 1;
    // WebSocket direct-connect addresses for each candidate (if registered)
    repeated WsAddressEntry ws_address_map = 2;
  }
  oneof result {
    RouteCandidatesOk success = 1;
    ErrorResponse error = 2;
  }
}

// =========================================================================
// Discovery request/response
// =========================================================================
// List (ActrType x fingerprint) combinations present on the network.
message DiscoveryRequest {
  optional string manufacturer = 1;
  optional uint32 limit = 2 [default = 64];
}

message DiscoveryResponse {
  message TypeEntry {
    required ActrType actr_type = 1;
    required string name = 2;
    optional string description = 3;
    required string service_fingerprint = 4;
    optional int64 published_at = 5;
    repeated string tags = 6;
  }

  message DiscoveryOk {
    repeated TypeEntry entries = 1;
  }

  oneof result {
    DiscoveryOk success = 1;
    ErrorResponse error = 2;
  }
}

// =========================================================================
// Get service spec request/response
// =========================================================================
message GetServiceSpecRequest {
  required string name = 1;
}

message GetServiceSpecResponse {
  oneof result {
    ServiceSpec success = 1;
    ErrorResponse error = 2;
  }
}

// =========================================================================
// Actr-up subscription (push-based presence by actor instance)
// =========================================================================

// Subscribe to "actor of target_type comes online" events.
message SubscribeActrUpRequest {
  required ActrType target_type = 1;
}

message SubscribeActrUpResponse {
  message SubscribeOk {}
  oneof result {
    SubscribeOk success = 1;
    ErrorResponse error = 2;
  }
}

// Cancel a previous subscription.
message UnsubscribeActrUpRequest {
  required ActrType target_type = 1;
}

message UnsubscribeActrUpResponse {
  message UnsubscribeOk {}
  oneof result {
    UnsubscribeOk success = 1;
    ErrorResponse error = 2;
  }
}

// Event: a matching actor instance changed presence.
message ActrUpEvent {
  required ActrId actor_id = 1;
}

// =========================================================================
// WebRTC Role Negotiation
// =========================================================================

// Initiate role arbitration between two Actr peers (who will create the Offer)
message RoleNegotiation {
  required ActrId from = 1;
  required ActrId to = 2;
  required uint32 realm_id = 3;
}

// Signaling server assigns the offerer role to one side
message RoleAssignment {
  required bool is_offerer = 1;
  optional bool remote_fixed = 2 [default = false];  // Whether remote peer has fixed network config
}

// =========================================================================
// ICE Restart Notification (Plan A: Answerer -> Offerer)
// =========================================================================

// Answerer notifies Offerer that its network has recovered,
// so the Offerer should immediately retry ICE restart instead of
// waiting for its backoff timer to expire.
message IceRestartRequest {
  optional string reason = 1; // e.g. "network_recovered", "network_type_changed"
}

// =========================================================================
// Credential Warning
// =========================================================================

// Credential warning used to notify clients about abnormal credential state.
message CredentialWarning {
  enum WarningType {
    KEY_IN_TOLERANCE_PERIOD = 0; // The key is in its tolerance period: expired but still usable.
  }
  required WarningType type = 1;
  required string message = 2;
}