Rule System — Sanki
===================

`reference` `suite-support`

This is a supporting document for the Sashité chess-family NIP suite. It does not define a new event kind or protocol mechanism; it describes **Sashité's rule system for the `sanki` game** as it is published — the Rule System event (kind `3417`) Sashité signs, the WebAssembly module that event names, what the module contains and reports, how it is built, verified, published and revised — under [ADR-0034](../../adr/adr-0034-reference-build.md).

The NIPs deliberately leave the *nature* of a rule system to the application layer: kind `3417` names a module by digest and states its ABI, and nothing more. This document is that application-layer statement for Sanki. It is the mechanised form of the *pluggable-arbiter clause* of the rules' playing principles — now that the suite has no arbiter ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)) and no manifest (ADR-0034), the clause reads: everything a publisher of a rule system may vary, it varies by publishing a new module; a session is played, to its end, under the bytes it was founded with.

## Scope

This document covers:

- what a Sanki rule system **is** — a module under the Kernel ABI, and the event that names it;
- what Sashité's reference module **contains** — the variants, the geometry, the parameters — and what its `describe` reports;
- how the module is **built**, **verified** and **published**, and how its Rule System event is signed;
- how it stays **available**, and what each consumer does with it;
- how a rule system is **revised**.

This document does NOT define:

- the ABI — [Kernel ABI — Sanki](kernel-abi-sanki.md) (`sashite.sanki.kernel-abi/1`) specifies the module's shape, its operations and its bounds;
- the kernel the module implements — [Kernel — Sanki](../../rules/kernel-sanki.md) and the documents it incorporates ([Move Encoding — Sanki](move-encoding-sanki.md), [Statuses — Sanki](statuses-sanki.md), [Time Accounting — Sanki](time-accounting-sanki.md), [Position Encoding — Sanki](position-encoding-sanki.md)) are the human-readable statement of what the module does;
- the Rule System event kind itself — kind `3417` defines the event, its constraints, its retrieval and its retention;
- the rules of the three Sanki variants as a reader learns them — those are the [rule pages](https://sashite.dev/rules/).

## What a rule system is

A rule system of the suite is a **WebAssembly module** implementing the Kernel ABI: a self-contained program that answers, for a session, every rule question the session raises — what is mechanically possible, what is legal, what a Ply does to the position, whether the position is terminal, how time is charged, which of a slot's candidates is canonical, what the verdict is at a cutoff. The module embeds its own tables and parameters; there is no manifest, no separately addressed geometry, and no kernel named apart from the code. **The verdict of a session is, by definition, the output of the module its rule system names on the session's public events**, and so is every intermediate answer (ADR-0034 §1; [Kernel ABI — Sanki](kernel-abi-sanki.md) §Normativity).

A rule system is *published* as a Rule System event (kind `3417`): a signed, immutable Nostr event carrying the module's SHA-256 (`x`), the ABI it implements (`abi`), the game it serves (`game`), retrieval hints (`url`) and, documentary, the specification it was built to (`spec`) and the sources it reproduces from (`source`). The **event id** is the identity of the rule system, the **digest** the identity of its behaviour, the **pubkey** its provenance. The founding events of a session name it by an `e` tag marked `rules`; the reference is a commitment for the players, a matching term for the matchmaker, and the one authority every Ply, Conclusion and certificate of the session is judged under (kind `3417` §Referencing a rule system).

The rule system is therefore **rule code, verified rather than believed**: a consumer that holds the event and the module holds everything needed to evaluate the session, forever, with no other software kept alive by anyone's discipline. A consumer that cannot retrieve either treats the session as unverified — never as non-conforming, and never evaluated by other means (kind `3417` §Retrieval and verification).

## Sashité's rule system for `sanki`

### The event

Sashité publishes its rule system under its own key — the key the Sashité client recognises by default (§Recognition and policy). The event carries:

| Tag | Value |
|---|---|
| `game` | `sanki` |
| `x` | the SHA-256 of the reference module's bytes |
| `abi` | `sashite.sanki.kernel-abi/1` |
| `url` | `https://blobs.sanki.app/<sha256>.wasm` (§Publication) |
| `spec` | the SHA-256 of *Kernel — Sanki* as published, with the documents it incorporates, and its URL on sashite.dev |
| `source` | the repository of the module's sources and the commit the digest reproduces from (§Build) |
| `nonce` | NIP-13 proof of work, at the suite's committed difficulty |

`content` is a label — `Sanki — Sashité reference rules, <year>-<month>` — and free notes on what changed since the previous rule system. NIP-94's `m` (`application/wasm`) and `size` are carried as a courtesy to consumers bounding their downloads. The event has no `expiration` tag, and a deletion request against it would be ignored (kind `3417` §Retention): sessions name it for their whole life.

### The module

The reference module is compiled from the reference engine — the code-first Rust implementation of the kernel — and nothing else:

- **`sashite-sanki-engine`** — the position kernel (Kernel — Sanki Part I): resolution of a ply against the geometry, legality, application, canonicalisation, terminal classification, clock arithmetic;
- **`sashite-sanki-session`** — the session kernel (Part II): the natural state, the two-window selection, the verdict at a cutoff, the check of a Conclusion, the canonical Conclusion;
- **`sashite-sanki-kernel-wasm`** — the ABI crate: the exports, the arena, the JSON encodings, the thirteen operations mapped onto the two crates above, and nothing computed of its own.

It embeds, as static data compiled in:

- the **GGN movement tables** of the three variants — the geometry the engine generates from its own definitions and is verified against by differential testing — served whole by `ggn`, about 1.2 MB of JSON per variant, in canonical form, and read at run time by nobody but the module;
- the **parameters** the kernel reads (Kernel — Sanki Appendix B), which `describe.parameters` reports in the kernel specification's own shape: per variant, the style letter, the piece vocabulary with the royal flagged, the initial fragments, the foot-soldier double-step data, the promotion rules, the drop rules (`letters`, `nifu`, `uchifuzume`), the capture mutation; per pairing, the dead-position predicates enabled; for the session, `stalemate = draw`, `repetition = 3`, `move_limit = 100`, `move_cap = 600`, the irreversible events (a capture, a foot-soldier move), and the selection cap `K = 8`.

None of this is an input. A consumer reads `describe` to learn what the module is and checks founding events against it; it computes nothing from `parameters` or `ggn`, which are documentary ([Kernel ABI — Sanki](kernel-abi-sanki.md) §`describe`).

### What `describe` reports

For Sashité's module:

| Member | Value | Standing |
|---|---|---|
| `game` | `sanki` | normative — equal to the event's `game` |
| `abi` | `sashite.sanki.kernel-abi/1` | normative — equal to the event's `abi` and to the `abi` export |
| `kernel` | `sashite.sanki.kernel/1` | documentary — the specification the module was built to |
| `max_step` | `300` | normative — no Ply with a larger `step` enters a chain (600 half-moves, `move_cap`) |
| `variants` | `chess` (style `W`), `ogi` (`J`), `xiongqi` (`C`), each with its piece vocabulary and initial fragments | identifiers and piece vocabulary normative; `style` and `initial` documentary |
| `positions` | the initial FEEN of each of the nine ordered pairings, `"<first variant>/<second variant>"` | normative — what a Game Session's `content` MUST equal (kind `3422` §Semantic constraints, item 7) |
| `parameters` | the parameters above | documentary |

The initial positions are **deterministic**: a Sanki rule system cannot describe a randomised-setup variant, since no party is designated to draw it ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)).

### Build

The module is built in a **pinned container** — the toolchain by version, the image by digest, the dependency graph by `Cargo.lock` — for `wasm32-unknown-unknown`, in release, with `panic = "abort"`, no debug information and source paths remapped, so that the bytes are a function of the sources and the recipe alone. The recipe, the source commit and the resulting digest are published together: in the module's repository, and in the event's `source` tag. Anyone can rebuild and compare — or, simpler, run the corpus and the differential against the published bytes. Reproducibility is auditability; the bytes named by `x` rule regardless (ADR-0034 §9).

Before a build is published it satisfies [Kernel ABI — Sanki](kernel-abi-sanki.md) §Conformance of a build, and the evidence is published with it: the WebAssembly profile and the fixed memory; the `abi` export; the whole conformance corpus but its `puzzle` category, run through the module in two runtimes — a browser's WebAssembly engine and `wasmi` — with byte-identical responses, the `puzzle` category through the validator with the module underneath; a random-session differential against the native engine; and the extreme session of the ABI's §Bounds completing in both runtimes. A build that fails any of these is not published. The corpus is the module's **test suite** ([Sanki conformance vectors](../conformance/README.md)); it no longer arbitrates between implementations, there being one.

A module is never rebuilt without a behavioural reason: a rebuild that reproduces the digest changes nothing, and one that does not is a new rule system.

### Publication

Publication is by commit, in this order:

1. **The blob.** The module is laid out on `https://blobs.sanki.app` under its digest — the read half of the [Blossom](https://github.com/hzrd149/blossom) protocol (BUD-01: `GET /<sha256>`, or `/<sha256>.wasm`), served as `application/wasm` — and uploaded to a Blossom server listed in Sashité's server list (BUD-03, kind `10063`), so that a consumer holding the event and knowing the publisher's key can construct `<server>/<sha256>.wasm` as any Blossom client does. The reference blob store is **append-only**: Sashité's commitment, recorded in the [Trust Model](trust-model.md) as the liveness assumption it is, that a blob once published is never removed nor replaced.
2. **The event.** The Rule System event is assembled with the digest, the ABI, the hints, the `spec` and `source` tags, mined to the suite's proof-of-work difficulty, signed with Sashité's key and published to the reference relays, which admit kind `3417` with proof of work and retain it indefinitely.
3. **The adoption.** The Sashité client's default founding terms, the matchmaker's accepted terms and the rating authority's recognised pools name the new event id from the next release on (§Revising a rule system).

The tool `nostr/tools/publish-rules.py` does the first step and prepares the second: it builds the module in the pinned container, prints the digest, lays the blob out under it and hands the unsigned event to a signer (`nak`, or a small tool holding Sashité's key), which mines, signs and publishes it and prints the event id. The manifest, the per-variant GGN blobs, `index.json` and the chain of incorporated documents that the digest design published are **retired** from publication: the prose is documentation on sashite.dev, named by the `spec` tag and read by people.

### Availability

A session is evaluable only while its rule system's event and module are retrievable, and both are meant to be retrievable for as long as any session, rating pool or Composition names them:

- the blob store is append-only and the module is on more than one server (§Publication); anyone may mirror it, and any consumer verifies what it gets;
- the reference relays retain kind `3417` indefinitely;
- every consumer **caches** the event and the module of every rule system it has a session under, by id and by digest — the client in browser storage, best-effort; the rating authority on disk, every module and every event of every pool it serves, indefinitely, a rating being a signed cache of a function of them (ADR-0034 §6).

## What a consumer does with it

- **A player's client**, before entering a pool, challenging, founding or accepting: retrieve the Rule System event and the module, verify both, instantiate the module and check its ABI — the "retrieve and execute" duty of kinds `3418`, `3420` and `3422` — and only then sign. During play, generate and validate moves, display the natural state, predict the verdict it would carry by concluding, and check the opponent's Conclusion, all by asking the module ([Kernel ABI — Sanki](kernel-abi-sanki.md) §The consumer's side).
- **A player concluding the session, and any verifier** ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)): the verdict is `verdict_at` on the session's public events, and a Conclusion (kind `3425`) is conforming if and only if `check` says so.
- **The matchmaker**: pair only Open Challenges whose `rules` references are equal — the same event id, whoever published it (kind `3419` §Consent constraints).
- **A rating authority**: partition its pools by rule-system event id ([Rating Specification — Elo](rating-specification-elo.md) / [Glicko-2](rating-specification-glicko2.md) §Rating pool), rate the canonical Conclusion (`select_conclusion`) and nothing else, and keep every module it has rated under.
- **A puzzle validator**: walk a certificate under the rule system the Composition names, through `legal_moves`, `apply` and `classify` (kind `3441`).

### Recognition and policy

The protocol trusts no publisher: the module's bytes are verified against the digest, and what the module computes is the rule. What the signature gives a consumer is *whose* rules a session is played under, on which each consumer bases a policy of its own — outside the protocol (ADR-0034 §7):

- the **Sashité client** founds under the rule system its build names, and **recognises** rule systems by publisher key — a set that is itself a client constant, Sashité's key by default. A session under a rule system by any other publisher is shown as such; the client instantiates an unrecognised publisher's module only with the user's consent, in the same sandbox, and under a wall-clock watchdog — a liveness guard for the client's own sake, never a verdict;
- the **rating authority** rates pools of recognised publishers only;
- the **matchmaker** pairs on equality of the reference, whoever published it.

A third party may publish a rule system of its own — its own module, or Sashité's module under its own event — and found sessions under it; the suite evaluates those sessions exactly as it evaluates Sashité's, and the policies above decide only who rates and who plays them.

## Revising a rule system

A rule system is never edited in place. A rebalancing, a fix, a new variant, a changed threshold, a changed selection rule, a new ABI: each is a **new module** with a new digest, published as a **new event** — there is one revision rule, and it has no exceptions (kind `3417` §Revision). A deployment that adopts it names the new event id from then on; sessions already founded keep theirs for their whole life, rematches inherit the concluded session's (kind `3420` §Rematch challenge), rating pools are partitioned accordingly, and the previous module stays on the blob store, exactly as it was, for the sessions that name it. Nothing about an existing session changes, which is what allows a session to be replayed years later from its public events, the event and the bytes alone.

The prose is revised in step: a new module is built to a new revision of *Kernel — Sanki*, whose digest the event's `spec` tag carries. A published module that turns out to contradict its prose is a **defect**, repaired forward by a new rule system; for the sessions that name it, the module is the rule (ADR-0034 §8).

## References

- [ADR-0034](../../adr/adr-0034-reference-build.md) — the rule system is a signed event naming an executable module
- [ADR-0033](../../adr/adr-0033-arbiterless-sessions.md) — arbiter-less sessions: why verdicts are verified, not believed
- [Rule System (kind `3417`)](https://github.com/sashite/nostr/blob/main/nips/3417-rule-system.md) — the event that names the module
- [Kernel ABI — Sanki](kernel-abi-sanki.md) — the interface of the module
- [Kernel — Sanki](../../rules/kernel-sanki.md) — the kernel the reference module implements
- [Sanki conformance vectors](../conformance/README.md) — the module's test suite
- [Trust Model](trust-model.md) — the publisher, the availability of the module, the append-only commitment
- Kind `3420` — Direct Challenge, §Match-terms tags (the `rules` reference); kind `3418` — Open Challenge; kind `3419` — Pairing; kind `3422` — Game Session; kind `3440` — Composition; kind `3441` — Validation
- [GGN v1.0.0](https://sashite.dev/specs/ggn/1.0.0/) — Geometry Gameplay Notation, the form of the embedded tables
- [Sashité rule pages](https://sashite.dev/rules/) — the human-readable rules of the three variants
- [Blossom](https://github.com/hzrd149/blossom) — content-addressed blob storage on Nostr (BUD-01, BUD-03)
