Skip to content

Commit 7018cda

Browse files
committed
fix: align skills with sails header routing
1 parent 1dbe65e commit 7018cda

31 files changed

Lines changed: 193 additions & 60 deletions

File tree

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: test-layout test-skills test-parser test-install test-packaging test-update verify
1+
.PHONY: test-layout test-skills test-parser test-install test-packaging test-update verify verify-real verify-all
22

33
test-layout:
44
python3 tests/test_repo_layout.py
@@ -21,3 +21,8 @@ test-update:
2121
python3 tests/test_update_check.py
2222

2323
verify: test-layout test-skills test-parser test-install test-packaging test-update
24+
25+
verify-real:
26+
bash scripts/verify-real-sails-program.sh
27+
28+
verify-all: verify verify-real

references/delayed-message-pattern.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,24 @@ Use this pattern for future-block work such as reminders, auctions, inactivity c
66

77
## Canonical Self-Message Payload
88

9-
For a standard Sails self-call by route name, encode service, method, and arguments in order, then concatenate:
9+
For a Sails 1.0 self-call, build the same Sails Header v1 plus SCALE-encoded params that generated clients use. In generated Rust client code this is the `io::<Call>::encode_call(route_idx, args...)` helper:
1010

1111
```rust
12-
let payload = [
13-
"ReminderBoard".encode(),
14-
"TriggerReminder".encode(),
15-
id.encode(),
16-
]
17-
.concat();
12+
use reminder_board_client::{
13+
ReminderBoardClientProgram,
14+
reminder_board::io::TriggerReminder,
15+
};
16+
17+
let payload = TriggerReminder::encode_call(
18+
ReminderBoardClientProgram::ROUTE_ID_REMINDER_BOARD,
19+
id,
20+
);
1821
```
1922

20-
- This is the route-prefixed byte shape to use when a delayed internal message cannot go through a generated client call directly.
21-
- Keep the service and method names aligned with the exported Sails routes.
22-
23-
Note: In Sails 1.0, messages use the binary header protocol instead of SCALE-string routing for the route prefix. See `../../references/sails-header-wire-format.md` for the encoding.
23+
- This is the header-routed byte shape to use when a delayed internal message cannot go through a generated client send directly.
24+
- Keep the route constant and generated `io` type aligned with the exported Sails route.
25+
- If generated client code is unavailable, manually encode the Sails Header v1 followed by SCALE-encoded params. See `../../references/sails-header-wire-format.md` for the header layout.
26+
- The legacy SCALE-string route form (`service`, `method`, then args) is not a valid Sails 1.0 delayed self-message payload.
2427

2528
## Sending The Delayed Message
2629

@@ -71,4 +74,4 @@ pub fn trigger_reminder(&mut self, id: u64) {
7174

7275
- Use `run_to_block` for delay-sensitive assertions.
7376
- Assert both the scheduler-side effect and the eventual handler-side effect.
74-
- If debugging the raw byte route, test the payload shape directly before blaming gas or timing.
77+
- If debugging the raw byte route, assert the payload starts with the Sails Header v1 bytes (`GM`, version `0x01`, header length `0x10`) before blaming gas or timing.

references/gear-builtin-actors.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ When debugging a builtin reply:
152152
1. Confirm the sender targeted a builtin `ActorId`, not a program. If it was a program, this skill does not apply — go to `gear-message-execution`.
153153
2. Identify the matching helper crate by source `ActorId` ↔ builtin id.
154154
3. Classify: empty success, typed `Response`, or error reply.
155-
4. Decode as `gbuiltin_*::Response` using `parity_scale_codec::Decode` — not a Sails generated client, not `ProgramMetadata`. There is no Sails route prefix on a builtin reply.
155+
4. Decode as `gbuiltin_*::Response` using `parity_scale_codec::Decode` — not a Sails generated client, not `ProgramMetadata`. There is no Sails Header framing on a builtin reply.
156156
5. On error replies, inspect the payload as `BuiltinActorError` (defined in the `builtins-common` crate at `vara/sdk/builtins/common/src/lib.rs`; import as `use builtins_common::BuiltinActorError;`). Common variants: `InsufficientGas` when the caller-supplied `gas_limit` is below the call's weight; `GasAllowanceExceeded` when the block-level allowance is exhausted; `InsufficientValue`; `DecodingError`; and `Custom(LimitedStr<'static>)` for actor-specific errors.
157157

158158
## Gas and ED
@@ -164,7 +164,7 @@ When debugging a builtin reply:
164164
## Guardrails
165165

166166
- **Do not hardcode an `ActorId` without citing a runtime file.** Re-derive with `hash((b"built/in", id).encode())` or copy from `vara/runtime/vara/src/lib.rs`, and note the source.
167-
- **Do not decode builtin replies with a Sails client.** There is no route prefix; use the helper crate's `Response` type.
167+
- **Do not decode builtin replies with a Sails client.** There is no Sails Header framing; use the helper crate's `Response` type.
168168
- **Do not call a builtin from a sync handler.** The call is `_for_reply`; the handler must be async.
169169
- **Do not assume a reply shape across runtime versions.** Helper crates evolve — pin to a workspace version that matches the target runtime.
170170
- **Do not wrap a builtin behind a service handler that loses idempotency.** Broker services should carry their own retry and reconciliation state; the builtin has no memory.

references/gear-execution-model.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
- Name actors, commands, queries, replies, events, and failure paths in specs before implementation starts.
2929
- Use delayed messages for future block work when the program really must revisit state later.
3030
- Use gas reservation only when future work needs a preserved execution budget across blocks.
31-
- Prefer generated Sails clients for normal app paths so route prefixes and reply decoding stay aligned with the program contract.
31+
- Prefer generated Sails clients for normal app paths so Sails Header routing and reply decoding stay aligned with the program contract.
3232

3333
## Validation Implications
3434

references/gear-gstd-api-and-syscalls.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Use `msg` when the design question is about payload handling, outbound messages,
3232
| Await a reply in async code | `_for_reply` helpers such as `msg::send_bytes_for_reply` | same send wrappers plus async runtime locks | send syscalls plus reply deposit or reply-hook handling |
3333
| Spend reserved gas on later messaging | reservation-backed send or reply variants | reservation send or reply wrappers | `gr_reservation_send`, `gr_reservation_send_commit`, `gr_reservation_reply`, `gr_reservation_reply_commit` |
3434

35-
Design note: prefer typed APIs for stable app contracts. Use bytes variants when the route is intentionally raw or when isolating codec or route-prefix bugs in Sails.
35+
Design note: prefer typed APIs for stable app contracts. Use bytes variants when the route is intentionally raw or when isolating codec or Sails Header routing bugs.
3636

3737
### `gstd::exec`
3838

references/gear-messaging-and-replies.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- Use delayed variants when execution must happen in a future block.
88
- Use reservation-backed variants when later execution needs preserved gas.
99
- Use staged payload flows only when the payload must be built incrementally.
10-
- When a Sails program calls a Sails constructor or service, prefer generated clients or equivalent route-prefixed encoding rather than an ad hoc raw struct payload.
10+
- When a Sails program calls a Sails constructor or service, prefer generated clients or equivalent Sails Header-aware encoding rather than an ad hoc raw struct payload.
1111

1212
## Message Lifecycle Checklist
1313

@@ -37,9 +37,9 @@
3737

3838
## Sails Routing Notes
3939

40-
- Generated Sails clients encode the correct service and method route prefixes for you.
41-
- The Sails wire format is route-prefixed encoded data, so a bare raw struct is not a normal constructor or service payload.
42-
- If a payload uses the wrong route prefix, decode fails even when the payload body type is otherwise correct.
40+
- Generated Sails clients encode the Sails Header and SCALE body for you.
41+
- The Sails 1.0 wire format is Sails Header v1 plus encoded data, so a bare raw struct is not a normal constructor or service payload.
42+
- If a payload uses the wrong interface ID, entry ID, or route index, decode fails even when the payload body type is otherwise correct.
4343
- Treat generated clients as the default path and use low-level byte encoding only to isolate transport or codec bugs.
4444

4545
## Guardrails

references/gear-sails-production-patterns.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ let reply = DemoProgram::client(program_id)
516516

517517
Use generated clients by default because they preserve:
518518

519-
- route-prefix correctness
519+
- Sails Header routing correctness
520520
- reply decoding
521521
- test mocks
522522
- consistency between Rust and TS consumers
@@ -533,12 +533,10 @@ fn send_timeout_from_reservation(
533533
player_id: ActorId,
534534
delay: u32,
535535
) {
536-
let request = [
537-
"Battle".encode(),
538-
"AutomaticMove".to_string().encode(),
539-
player_id.encode(),
540-
]
541-
.concat();
536+
let request = battle_client::battle::io::AutomaticMove::encode_call(
537+
battle_client::BattleClientProgram::ROUTE_ID_BATTLE,
538+
player_id,
539+
);
542540

543541
msg::send_bytes_delayed_from_reservation(
544542
reservation_id,
@@ -553,12 +551,10 @@ fn send_timeout_from_reservation(
553551

554552
```rust
555553
fn send_cleanup(player: ActorId, gas: u64, delay: u32) {
556-
let payload = [
557-
"Game".encode(),
558-
"RemoveInstance".encode(),
559-
player.encode(),
560-
]
561-
.concat();
554+
let payload = game_client::game::io::RemoveInstance::encode_call(
555+
game_client::GameClientProgram::ROUTE_ID_GAME,
556+
player,
557+
);
562558

563559
msg::send_bytes_with_gas_delayed(Syscall::program_id(), payload, gas, 0, delay)
564560
.expect("Error in sending message");

references/sails-cheatsheet.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ These errors appear in order of frequency from builder feedback.
5858
## IDL And Clients
5959
- Sails generates IDL from Rust types at build time.
6060
- Generated clients are the default typed integration surface for Rust and TypeScript.
61-
- Generated clients encode the correct payloads for constructor and service calls using route-prefixed SCALE encoding.
61+
- Generated clients encode the correct payloads for constructor and service calls using the Sails Header plus SCALE body.
6262
- The IDL uses V2 syntax (see `../../references/sails-idl-v2-syntax.md`) and messages use a binary header protocol (see `../../references/sails-header-wire-format.md`).
6363
- Architecture decisions must keep exported DTO names distinct from service names.
6464
- Events are part of the public interface and should map to meaningful state transitions.
@@ -67,7 +67,7 @@ These errors appear in order of frequency from builder feedback.
6767
- Specs should talk in terms of program constructors, service routes, commands, queries, and events.
6868
- Specs should name the chosen state ownership pattern instead of leaving storage implicit.
6969
- Architecture plans should keep `#[program]` thin and push logic into services.
70-
- Implementation guidance should prefer generated clients or other Sails route-prefixed encoding over raw payload handling.
70+
- Implementation guidance should prefer generated clients or other Sails Header-aware encoding over raw payload handling.
7171

7272
## See Also
7373
- `references/sails-rs-imports.md`

references/sails-header-wire-format.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@ Source of truth: [docs/sails-header-v1-spec.md](https://github.com/gear-tech/sai
44

55
## Overview
66

7-
Every Sails message begins with a 16-byte base header (extensions optional) prepended to the SCALE-encoded payload. The header lives entirely within the Gear message payload — no runtime or consensus changes required.
7+
Every Sails 1.0 message begins with a 16-byte header prepended to the SCALE-encoded payload. The header lives entirely within the Gear message payload — no runtime or consensus changes required.
88

99
## Base Header Layout
1010

1111
```
1212
Byte offset Field Size (bytes) Description
1313
0–1 Magic 2 ASCII "GM" (0x47 0x4D)
1414
2 Version 1 0x01
15-
3 Header length 1 Total header size; base = 0x10
15+
3 Header length 1 Must be 0x10 in v1
1616
4–11 Interface ID 8 64-bit ID from interface hash
1717
12–13 Entry ID 2 Little-endian 16-bit entry identifier
1818
14 Route Index 1 0x00 = infer route if unambiguous
1919
15 Reserved 1 Must be 0x00 in v1
20-
>15 Extensions variable Present only if header length > 0x10
20+
>15 Payload variable SCALE-encoded params or body
2121
```
2222

23+
Extension-sized headers are reserved for future versions. A v1 decoder must reject header lengths other than `0x10`.
24+
2325
## Identifier Derivation
2426

2527
### Interface ID
@@ -42,15 +44,15 @@ Generated clients encode/decode the header automatically. When debugging wire pa
4244
## Interface ID Stability
4345

4446
- Adding or removing methods/types **changes** the interface ID
45-
- Renaming methods does NOT change the interface ID (hash is structural)
46-
- `@entry_id` overrides allow pinning specific entry points across versions
47+
- Renaming public methods **changes** the interface ID because the exported function route name is hashed
48+
- `@entry_id` overrides allow pinning specific entry points across versions, but they do not preserve the interface ID after a method rename
4749
- Programs hosting V1 and V2 services simultaneously can use `route_idx` to disambiguate
4850

4951
## Validation Checklist
5052

5153
1. Magic: first two bytes are `0x47 0x4D`
5254
2. Version: `0x01`
53-
3. Header length: `= 0x10` for v1; `> 0x10` only when extensions are present and must be `<= payload_length`
55+
3. Header length: must equal `0x10` for v1; reject smaller, larger, or extension-sized v1 headers
5456
4. Reserved byte: must be `0x00` in v1
5557
5. Route inference (`0x00`): resolve only if exactly one matching instance exists
5658

references/sails-idl-v2-syntax.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,18 @@ struct Point<T> { x: T, y: T }
6767
enum Result<T, E> { Ok(T), Err(E) }
6868
```
6969

70-
### Built-in Aliases
70+
### Built-in Primitives And Common Aliases
7171

72-
`actor` = `ActorId([u8; 32])`, `code` = `CodeId([u8; 32])`, `u256` = `U256([u64; 4])`, `h160` = `H160([u8; 20])`, `h256` = `H256([u8; 32])`, `map<K,V>` = `[(K,V)]`, `set<T>` = `[T]`
72+
Built-in primitives include `actor` = `ActorId([u8; 32])`, `code` = `CodeId([u8; 32])`, `messageid` = `MessageId([u8; 32])`, `u256` = `U256([u64; 4])`, `h160` = `H160([u8; 20])`, and `h256` = `H256([u8; 32])`.
73+
74+
`map<K,V>` and `set<T>` are not built in. Declare them in a `types` block before use:
75+
76+
```
77+
types {
78+
alias map<K,V> = [(K,V)];
79+
alias set<T> = [T];
80+
}
81+
```
7382

7483
## Service
7584

@@ -170,6 +179,8 @@ service Canvas {
170179
}
171180
172181
types {
182+
alias map<K,V> = [(K,V)];
183+
alias set<T> = [T];
173184
struct Color { color: [u8; 4], space: ColorSpace }
174185
enum ColorError { InvalidSource, DeadPoint }
175186
struct Point<T> { x: T, y: T }

0 commit comments

Comments
 (0)