From 1652afad11335c30a0227fde5981706b895b067a Mon Sep 17 00:00:00 2001 From: acamilo Date: Tue, 22 Sep 2026 11:41:04 +0000 Subject: [PATCH] test(session-types): the CONTRACT-01 fixtures, cross-checks and their Rust tests fixtures/ is the shared contract corpus: accepted payloads with their canonical JSON and digest, refused payloads with the rule each breaks, raw byte sequences that must not even parse, recipes for the payloads too large to store, the U64 and double boundaries, rational arithmetic vectors, the identity matrix, descriptor cross-checks, operation keys and bodies, trace comparison variants, the schema set and its digest, seed vectors and one FLYSESS1 envelope. packages/session-types loads the same files. Tests: 45 across nine files. Round trips preserve every field and reproduce the recorded canonical bytes; duplicate keys, invalid UTF-8, NaN, trailing data, oversize typed values and oversize envelopes fail; U64 and double boundaries reject on the right side; the four identities never accept one another spellings; the accumulator produces 16, 17, 17 and a zero remainder; the trace comparator ignores wall time, request ids, callIds and delivery ids and notices every behaviour change; the contract digest survives reformatting and moves when a field, bound, enum member or type changes. Also: a crate README, and the update_fixtures example that regenerates the derived files. --- .../flysim/crates/fly-session-types/README.md | 103 + .../examples/update_fixtures.rs | 290 ++ .../fixtures/boundaries.json | 153 + .../fixtures/checkpoint-envelope.json | 195 + .../fixtures/contract-digest.json | 9 + .../fixtures/descriptor-checks.json | 2085 +++++++++ .../fly-session-types/fixtures/generated.json | 74 + .../fixtures/identities.json | 111 + .../fly-session-types/fixtures/invalid.json | 3900 +++++++++++++++++ .../fixtures/operations.json | 667 +++ .../fly-session-types/fixtures/rational.json | 249 ++ .../fly-session-types/fixtures/raw.json | 77 + .../fixtures/schema-set.json | 1 + .../fixtures/seed-vectors.json | 185 + .../fly-session-types/fixtures/traces.json | 1141 +++++ .../fly-session-types/fixtures/valid.json | 2787 ++++++++++++ .../fly-session-types/tests/canonical_json.rs | 192 + .../tests/checkpoint_envelope.rs | 165 + .../fly-session-types/tests/common/mod.rs | 133 + .../tests/descriptor_checks.rs | 78 + .../fly-session-types/tests/encodings.rs | 169 + .../fly-session-types/tests/payloads.rs | 177 + .../fly-session-types/tests/rational.rs | 140 + .../fly-session-types/tests/schema_set.rs | 187 + .../crates/fly-session-types/tests/seeds.rs | 120 + .../crates/fly-session-types/tests/traces.rs | 113 + 26 files changed, 13501 insertions(+) create mode 100644 services/flysim/crates/fly-session-types/README.md create mode 100644 services/flysim/crates/fly-session-types/examples/update_fixtures.rs create mode 100644 services/flysim/crates/fly-session-types/fixtures/boundaries.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/checkpoint-envelope.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/contract-digest.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/descriptor-checks.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/generated.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/identities.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/invalid.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/operations.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/rational.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/raw.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/schema-set.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/seed-vectors.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/traces.json create mode 100644 services/flysim/crates/fly-session-types/fixtures/valid.json create mode 100644 services/flysim/crates/fly-session-types/tests/canonical_json.rs create mode 100644 services/flysim/crates/fly-session-types/tests/checkpoint_envelope.rs create mode 100644 services/flysim/crates/fly-session-types/tests/common/mod.rs create mode 100644 services/flysim/crates/fly-session-types/tests/descriptor_checks.rs create mode 100644 services/flysim/crates/fly-session-types/tests/encodings.rs create mode 100644 services/flysim/crates/fly-session-types/tests/payloads.rs create mode 100644 services/flysim/crates/fly-session-types/tests/rational.rs create mode 100644 services/flysim/crates/fly-session-types/tests/schema_set.rs create mode 100644 services/flysim/crates/fly-session-types/tests/seeds.rs create mode 100644 services/flysim/crates/fly-session-types/tests/traces.rs diff --git a/services/flysim/crates/fly-session-types/README.md b/services/flysim/crates/fly-session-types/README.md new file mode 100644 index 0000000..72ff894 --- /dev/null +++ b/services/flysim/crates/fly-session-types/README.md @@ -0,0 +1,103 @@ +# fly-session-types + +The executable schemas of the session framework: domain scalars, closed enums, method +payloads, canonical JSON, canonical digests and the step trace format. + +This crate is CONTRACT-01 of +[`docs/design/session-framework/implementation.md`](../../../../docs/design/session-framework/implementation.md). +It holds no transport, no worker, no coordinator and no store; it never opens a socket or a +file other than its own fixtures. The bus owns the wire +([`flybus`](../flybus)), and this crate owns what the messages mean. + +## Layout + +| Module | Contents | +| --- | --- | +| `scalar` | `Scope`, `RationalNs`, `SchemaRef`, `TypedValue`, the `DomainType` trait, and `BusCallId` / `DomainRequestId` / `ArtifactIdentity` / `OwnerToken` | +| `canonical` | RFC 8785 canonical JSON, SHA-256 digests, `OperationKey`, canonical bodies, the 64-KiB envelope check | +| `rpc` | `SessionRpcRequest`, `SessionRpcSuccess`, `SessionRpcFailure`, `ErrorCode`, `MutationCertainty` | +| `workers` | The closed enums and every Agent/Environment/Worker method payload of workers-v1 | +| `media` | `ViewDescriptor`, `ViewRef`, `AudioDescriptor`, `AudioRef` and the `State.*` payloads | +| `publishing` | `SessionDescriptor` and `CommittedSnapshot` | +| `trace` | `TraceBehaviour`, `TraceOperational`, `TransitionTrace` and the behaviour comparator | +| `schema` | The canonical schema set and `contract_digest()` | +| `seed` | `seed-derivation-v1` | +| `checkpoint` | The `FLYSESS1` envelope layout | +| `fixtures` | Loading `fixtures/`, shared with `packages/session-types` | + +`Id`, `U64` and `Digest` are the bus encodings: `scalar` calls into `flybus::wire` instead of +restating them, and `tests/encodings.rs` pins that the two agree for every edge case. + +## Reading and validating + +Every type implements `DomainType`: + +```rust +use fly_session_types::scalar::{DomainType, Scope}; + +let scope = Scope::from_json(&value)?; // reads, refusing unknown fields, then validates +scope.validate()?; // the cross-field rules, re-runnable +let json = scope.to_json(); // the canonical shape +``` + +Rules that need another value in hand are separate, because a payload cannot check them alone: + +```rust +control.validate_against(&port.controls)?; // complete batch, descriptor order, ranges +input.validate_against(&descriptor.views)?; // max(0, boundary - observationDelaySteps) +result.validate_against(&descriptor, &previous)?; // exactly one stepDuration of world time +snapshot.validate_against(&session_descriptor)?; // revision, agent set, assigned ports +telemetry.validate_against_roles(&profile_roles)?; // rates in profile-defined order +``` + +## Digests + +- `contract_digest()` is the SHA-256 of the canonical schema set (`schema::schema_set()`), + which is a declaration: type names, JSON field names, kinds, bounds and closed enums. + Reformatting this crate cannot change it; changing a field or a bound does. +- `canonical::body_digest(method, scope, params)` is the comparison ipc-v1 section 5 uses to + tell a safe replay from a `CONFLICT`. It refuses a body that carries a bus identity. +- `OperationKey` is `(sessionId, epoch, step, method, workerId)`, and deliberately not the + request id: a changed id for an existing key is the conflict to detect. + +## Fixtures + +`fixtures/` is loaded by these tests and by `packages/session-types`, so a case is written +once and holds both languages to it. + +| File | Contents | +| --- | --- | +| `valid.json` | Payloads every implementation accepts, with their canonical JSON and digest | +| `invalid.json` | Payloads every implementation refuses, each with the rule it breaks | +| `raw.json` | Byte sequences refused before validation: duplicate keys, invalid UTF-8, `NaN`, trailing data | +| `generated.json` | Recipes for payloads too large to store: the 32-KiB and 64-KiB boundaries, 512-code-point messages | +| `boundaries.json` | The `U64` decimal-string and double boundaries | +| `rational.json` | Checked rational arithmetic and the 16, 17, 17 tick accumulator | +| `identities.json` | Which of the four identity types accepts which spelling | +| `descriptor-checks.json` | Rules that need a descriptor: batches, delays, byte shapes, descriptor agreement | +| `operations.json` | Operation keys, canonical bodies and the pairs that are or are not the same operation | +| `traces.json` | A baseline transition and the variants that must or must not compare equal | +| `schema-set.json`, `contract-digest.json` | The canonical schema set and its digest | +| `seed-vectors.json` | `seed-derivation-v1` test vectors | +| `checkpoint-envelope.json` | One `FLYSESS1` envelope, its layout and the corruptions a reader refuses | + +The derived files (`schema-set.json`, `contract-digest.json`, the `canonical`/`digest` fields +of `valid.json`, the digests in `operations.json`, `seed-vectors.json` and +`checkpoint-envelope.json`) come from +`cargo run -p fly-session-types --example update_fixtures`; +`tests/schema_set.rs` fails if the checked-in files are stale. + +## Tests + +```sh +cargo test -p fly-session-types +cargo clippy -p fly-session-types --all-targets +``` + +## Bounds this crate chose + +Every bound in the schema set names its source. Six are marked `crate` because no document +states them: `maxAudioStreams` (8), `maxCapabilities` (32), `maxSupportedMajors` (8), +`maxSupportedStimuli` (64), `maxAssets` (64) and `maxSnapshotEvents` (64). They exist so an +unbounded array cannot fill an envelope, and they are in the digest, so widening one is a +contract change rather than a quiet edit. diff --git a/services/flysim/crates/fly-session-types/examples/update_fixtures.rs b/services/flysim/crates/fly-session-types/examples/update_fixtures.rs new file mode 100644 index 0000000..238287e --- /dev/null +++ b/services/flysim/crates/fly-session-types/examples/update_fixtures.rs @@ -0,0 +1,290 @@ +//! Regenerates the derived fixture files. +//! +//! `cargo run -p fly-session-types --example update_fixtures`. `tests/fixtures_current.rs` +//! fails if the checked-in files differ from what this writes, so the digests in the +//! fixtures can never drift from the code that produced them. + +use std::collections::BTreeMap; + +use fly_session_types::scalar::{DomainType, Scope}; +use fly_session_types::{canonical, checkpoint, fixtures, schema, seed}; +use serde_json::{Map, Value, json}; + +fn main() { + let dir = fixtures::dir(); + for (name, contents) in derived() { + let path = dir.join(&name); + std::fs::write(&path, contents).expect("write fixture"); + println!("wrote {}", path.display()); + } +} + +/// Every derived fixture, as `(file name, exact bytes)`. +pub fn derived() -> Vec<(String, String)> { + vec![ + ("schema-set.json".to_owned(), schema_set()), + ("contract-digest.json".to_owned(), contract_digest()), + ("valid.json".to_owned(), valid()), + ("operations.json".to_owned(), operations()), + ("seed-vectors.json".to_owned(), seed_vectors()), + ("checkpoint-envelope.json".to_owned(), checkpoint_envelope()), + ] +} + +fn write(value: &Value) -> String { + let mut text = serde_json::to_string_pretty(value).expect("serializable"); + text.push('\n'); + text +} + +fn schema_set() -> String { + // The rendered set is itself canonical JSON, so the file the TypeScript package hashes is + // byte for byte what the digest was taken over. + let mut text = schema::schema_set_json().expect("canonicalizable"); + text.push('\n'); + text +} + +fn contract_digest() -> String { + let set = schema::schema_set_json().expect("canonicalizable"); + write(&json!({ + "description": "contractDigest is the SHA-256 of the canonical schema set in schema-set.json.", + "contractDigest": schema::contract_digest(), + "schemaSetVersion": schema::SCHEMA_SET_VERSION, + "schemaSetBytes": set.len(), + "types": schema::SCHEMAS.len(), + "enums": schema::ENUMS.len(), + "limits": schema::LIMITS.len(), + })) +} + +fn valid() -> String { + let mut file = fixtures::load("valid.json").expect("valid.json"); + let cases = file + .get_mut("cases") + .and_then(Value::as_array_mut) + .expect("cases"); + for case in cases.iter_mut() { + let value = case.get("value").expect("value").clone(); + let canonical = canonical::canonicalize(&value).expect("canonicalizable"); + let digest = canonical::sha256_hex(canonical.as_bytes()); + let map = case.as_object_mut().expect("case object"); + map.insert("canonical".to_owned(), Value::String(canonical)); + map.insert("digest".to_owned(), Value::String(digest)); + } + write(&file) +} + +fn operations() -> String { + let mut file = fixtures::load("operations.json").expect("operations.json"); + let scope_of = |case: &Value| -> Option { + match case.get("scope") { + Some(Value::Null) | None => None, + Some(v) => Some(Scope::from_json(v).expect("scope")), + } + }; + for key in file + .get_mut("keys") + .and_then(Value::as_array_mut) + .expect("keys") + { + let scope = scope_of(key).expect("an operation key has a scope"); + let method = key.get("method").and_then(Value::as_str).expect("method"); + let worker = key.get("workerId").and_then(Value::as_str).expect("workerId"); + let digest = canonical::OperationKey::new(scope, method, worker) + .expect("valid key") + .digest() + .expect("digest"); + key.as_object_mut() + .expect("object") + .insert("digest".to_owned(), Value::String(digest)); + } + for body in file + .get_mut("bodies") + .and_then(Value::as_array_mut) + .expect("bodies") + { + let scope = scope_of(body); + let method = body.get("method").and_then(Value::as_str).expect("method"); + let params = body.get("params").expect("params").clone(); + let digest = + canonical::body_digest(method, scope.as_ref(), ¶ms).expect("canonical body"); + body.as_object_mut() + .expect("object") + .insert("digest".to_owned(), Value::String(digest)); + } + write(&file) +} + +fn seed_vectors() -> String { + let master_seeds: [u64; 5] = [0, 1, 42, 9_223_372_036_854_775_808, u64::MAX]; + let agents = ["fly-a", "fly-b", "fly-c", "fly-d"]; + let mut vectors = Vec::new(); + for master in master_seeds { + for agent in agents { + let material = seed::material(master, agent).expect("material"); + vectors.push(json!({ + "masterSeed": master.to_string(), + "agentId": agent, + "material": String::from_utf8(material).expect("utf-8"), + "materialDigest": seed::material_digest(master, agent).expect("digest"), + "seed": seed::agent_seed(master, agent).expect("seed"), + })); + } + } + let composition: Vec = seed::composition_seeds( + 42, + &agents.iter().map(|a| (*a).to_owned()).collect::>(), + ) + .expect("composition") + .into_iter() + .map(Value::from) + .collect(); + write(&json!({ + "description": "seed-derivation-v1 test vectors. Both languages must reproduce every seed.", + "algorithm": seed::ALGORITHM, + "prefix": seed::PREFIX, + "materialTemplate": "\\n\\n\\n", + "rule": "SHA-256 of the material, read as eight big-endian u32 lanes; the first nonzero lane is the seed as a two's-complement i32.", + "vectors": vectors, + "composition": { + "masterSeed": "42", + "agentIds": agents, + "seeds": composition, + "reason": "independent per-agent seeds from one recorded master seed and stable agent ids", + }, + "invalid": [ + {"masterSeed": "0", "agentId": "Fly-A", "reason": "an agent id is an Id: lowercase"}, + {"masterSeed": "0", "agentId": "", "reason": "an agent id is 1..=64 characters"}, + {"masterSeed": "0", "agentIds": ["fly-a", "fly-a"], + "reason": "a composition with a repeated agent id is refused rather than silently sharing a seed"}, + ], + })) +} + +fn checkpoint_envelope() -> String { + let scope = Scope::new("demo", "epoch-1", 42).expect("scope"); + let manifest = json!({ + "envelopeVersion": checkpoint::VERSION, + "checkpointId": "ckpt-1", + "sourceScope": scope.to_json(), + "episodeId": "episode-1", + "worldTime": {"numerator": "700000000", "denominator": "1"}, + "schedulerId": "lockstep-v1", + "compositionDigest": canonical::sha256_hex(b"composition"), + "portMap": [{"portId": "port-1", "agentId": "fly-a"}], + "compatibility": { + "backendDigest": canonical::sha256_hex(b"backend"), + "contentDigest": canonical::sha256_hex(b"content"), + "patchDigest": canonical::sha256_hex(b"patch"), + "controllerDigest": canonical::sha256_hex(b"controller"), + "parserDigest": canonical::sha256_hex(b"parser"), + "stateFormatId": "flysess-1", + }, + "agents": [{ + "agentId": "fly-a", + "profileDigest": canonical::sha256_hex(b"profile"), + "datasetDigest": canonical::sha256_hex(b"fafb-v783"), + "modelVersion": "lif-1ms-f64-v2", + "plasticityVersion": "fly-kc-mbon-rstdp-v2", + "seed": seed::agent_seed(42, "fly-a").expect("seed"), + "brainTicks": "2534", + "remainder": {"numerator": "1000000", "denominator": "3"}, + "payload": "agent-fly-a", + }], + "coordinator": { + "taskLedger": "task-ledger", + "priorInspection": "prior-inspection", + "executorState": [{"agentId": "fly-a", "payload": "executor-fly-a"}], + "admissionState": null, + "eventWatermarks": {"lastEventId": "evt-1", "lastOrdinal": "7"}, + }, + "helperState": [], + "payloads": payload_table(), + }); + let bytes = checkpoint::encode(&manifest, &payloads()).expect("encode"); + let envelope = checkpoint::decode(&bytes).expect("decode"); + let entries: Vec = envelope + .layout + .entries + .iter() + .map(|entry| { + json!({ + "name": entry.name, + "offset": entry.offset.to_string(), + "byteLength": entry.byte_length.to_string(), + "digest": checkpoint::hex(&entry.digest), + }) + }) + .collect(); + let first_payload = envelope.layout.entries[0].offset; + write(&json!({ + "description": "One FLYSESS1 envelope, its layout and the corruptions a reader must refuse.", + "magic": "FLYSESS1", + "footerMagic": "FLYSESSF", + "version": checkpoint::VERSION, + "manifest": manifest, + "payloads": payloads() + .iter() + .map(|(name, bytes)| json!({"name": name, "base64": fixtures::encode_base64(bytes)})) + .collect::>(), + "envelope": { + "base64": fixtures::encode_base64(&bytes), + "byteLength": bytes.len(), + "layout": { + "headerBytes": checkpoint::HEADER_BYTES, + "manifestOffset": envelope.layout.manifest_offset.to_string(), + "manifestBytes": envelope.layout.manifest_bytes, + "tableOffset": envelope.layout.table_offset.to_string(), + "tableEntryBytes": checkpoint::TABLE_ENTRY_BYTES, + "entries": entries, + "footerOffset": envelope.layout.footer_offset.to_string(), + "footerBytes": checkpoint::FOOTER_BYTES, + "totalBytes": envelope.layout.total_bytes.to_string(), + }, + }, + "corruption": [ + {"name": "a flipped magic byte", "offset": 0, "reason": "wrong magic"}, + {"name": "an unsupported version", "offset": 8, "reason": "unsupported version"}, + {"name": "a flipped manifest byte", "offset": checkpoint::HEADER_BYTES, + "reason": "the footer digest covers the manifest"}, + {"name": "a flipped payload byte", "offset": first_payload, + "reason": "every payload carries its own digest"}, + {"name": "a flipped footer digest byte", "offset": bytes.len() - 40, + "reason": "the footer digest must match the contents"}, + {"name": "a flipped footer magic byte", "offset": bytes.len() - 8, + "reason": "a truncated file cannot look complete"}, + ], + })) +} + +fn payloads() -> Vec<(String, Vec)> { + vec![ + ("agent-fly-a".to_owned(), b"agent state bytes".to_vec()), + ("executor-fly-a".to_owned(), b"executor state".to_vec()), + ("task-ledger".to_owned(), b"{\"rank\":10}".to_vec()), + ("prior-inspection".to_owned(), b"{\"map\":40}".to_vec()), + ("world".to_owned(), vec![0u8; 64]), + ] +} + +fn payload_table() -> Value { + let mut out = Vec::new(); + for (name, bytes) in payloads() { + let mut entry = Map::new(); + entry.insert("name".to_owned(), Value::String(name)); + entry.insert( + "byteLength".to_owned(), + Value::String(bytes.len().to_string()), + ); + entry.insert( + "digest".to_owned(), + Value::String(canonical::sha256_hex(&bytes)), + ); + out.push(Value::Object(entry)); + } + // A BTreeMap would sort the payload names; the table order is the write order, which is + // what the envelope records. + let _: BTreeMap<(), ()> = BTreeMap::new(); + Value::Array(out) +} diff --git a/services/flysim/crates/fly-session-types/fixtures/boundaries.json b/services/flysim/crates/fly-session-types/fixtures/boundaries.json new file mode 100644 index 0000000..e211549 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/boundaries.json @@ -0,0 +1,153 @@ +{ + "description": "The U64 decimal-string and double boundaries, shared by both languages.", + "u64": [ + { + "text": "0", + "accept": true, + "reason": "zero is \"0\"" + }, + { + "text": "1", + "accept": true, + "reason": "" + }, + { + "text": "18446744073709551615", + "accept": true, + "reason": "the U64 maximum" + }, + { + "text": "18446744073709551616", + "accept": false, + "reason": "one past the maximum" + }, + { + "text": "184467440737095516150", + "accept": false, + "reason": "far past the maximum" + }, + { + "text": "00", + "accept": false, + "reason": "no leading zeros" + }, + { + "text": "01", + "accept": false, + "reason": "no leading zeros" + }, + { + "text": "", + "accept": false, + "reason": "empty" + }, + { + "text": "-1", + "accept": false, + "reason": "unsigned" + }, + { + "text": "+1", + "accept": false, + "reason": "no sign" + }, + { + "text": "1.0", + "accept": false, + "reason": "integers only" + }, + { + "text": "1e3", + "accept": false, + "reason": "decimal digits only" + }, + { + "text": " 1", + "accept": false, + "reason": "no whitespace" + }, + { + "text": "1 ", + "accept": false, + "reason": "no whitespace" + }, + { + "text": "0x10", + "accept": false, + "reason": "decimal only" + }, + { + "text": "9007199254740993", + "accept": true, + "reason": "a U64 string keeps precision a double would lose" + } + ], + "doubles": [ + { + "value": 0.0, + "canonical": "0", + "accept": true, + "reason": "" + }, + { + "value": -0.0, + "canonical": "0", + "accept": true, + "reason": "JSON.stringify prints negative zero as 0" + }, + { + "value": 1.0, + "canonical": "1", + "accept": true, + "reason": "an integral double prints without a fraction" + }, + { + "value": 0.1, + "canonical": "0.1", + "accept": true, + "reason": "" + }, + { + "value": 1e+21, + "canonical": "1e+21", + "accept": true, + "reason": "ECMAScript switches to exponent notation at 1e21" + }, + { + "value": 1e-07, + "canonical": "1e-7", + "accept": true, + "reason": "" + }, + { + "value": 5e-324, + "canonical": "5e-324", + "accept": true, + "reason": "the smallest subnormal double" + }, + { + "value": 1.7976931348623157e+308, + "canonical": "1.7976931348623157e+308", + "accept": true, + "reason": "the largest finite double" + }, + { + "value": 9007199254740991, + "canonical": "9007199254740991", + "accept": true, + "reason": "the largest exactly representable integer" + }, + { + "value": 9007199254740993, + "canonical": null, + "accept": false, + "reason": "past the exact integer range, canonical JSON refuses it" + }, + { + "value": 0.30000000000000004, + "canonical": "0.30000000000000004", + "accept": true, + "reason": "shortest round-tripping form, not a rounded one" + } + ] +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/checkpoint-envelope.json b/services/flysim/crates/fly-session-types/fixtures/checkpoint-envelope.json new file mode 100644 index 0000000..6636bae --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/checkpoint-envelope.json @@ -0,0 +1,195 @@ +{ + "description": "One FLYSESS1 envelope, its layout and the corruptions a reader must refuse.", + "magic": "FLYSESS1", + "footerMagic": "FLYSESSF", + "version": 1, + "manifest": { + "envelopeVersion": 1, + "checkpointId": "ckpt-1", + "sourceScope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "schedulerId": "lockstep-v1", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "portMap": [ + { + "portId": "port-1", + "agentId": "fly-a" + } + ], + "compatibility": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "patchDigest": "a4895eb44afc336fecbba6e520cd67e178dace0276655d102fceffa8e5f70570", + "controllerDigest": "c1472135b14c77c8bef98e73f70208325fa0dcf1e6bd668ae9b31a9cea295fe7", + "parserDigest": "b17d45121150928f2146af49e195eff1eef5d67325be273a733fb74acadaa342", + "stateFormatId": "flysess-1" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "modelVersion": "lif-1ms-f64-v2", + "plasticityVersion": "fly-kc-mbon-rstdp-v2", + "seed": -184946063, + "brainTicks": "2534", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "payload": "agent-fly-a" + } + ], + "coordinator": { + "taskLedger": "task-ledger", + "priorInspection": "prior-inspection", + "executorState": [ + { + "agentId": "fly-a", + "payload": "executor-fly-a" + } + ], + "admissionState": null, + "eventWatermarks": { + "lastEventId": "evt-1", + "lastOrdinal": "7" + } + }, + "helperState": [], + "payloads": [ + { + "name": "agent-fly-a", + "byteLength": "17", + "digest": "1321dffb0cdc6f9092cbf7fa2a5fc68bbed12c993d5ad398264012810ce9bf93" + }, + { + "name": "executor-fly-a", + "byteLength": "14", + "digest": "3aee60df7e29efeba7f5f99fc5867647b36aebff1d5d3c838dbff323122e6462" + }, + { + "name": "task-ledger", + "byteLength": "11", + "digest": "40b00ed2bbba901d68205ff71b04a44b9ee53c51cb3109aa2ceaa44f1c45727e" + }, + { + "name": "prior-inspection", + "byteLength": "10", + "digest": "2c13b7b4d9a9916801ab9191c314f31b045e9b9c5b669a6c0474f0217ef75bf5" + }, + { + "name": "world", + "byteLength": "64", + "digest": "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + } + ] + }, + "payloads": [ + { + "name": "agent-fly-a", + "base64": "YWdlbnQgc3RhdGUgYnl0ZXM=" + }, + { + "name": "executor-fly-a", + "base64": "ZXhlY3V0b3Igc3RhdGU=" + }, + { + "name": "task-ledger", + "base64": "eyJyYW5rIjoxMH0=" + }, + { + "name": "prior-inspection", + "base64": "eyJtYXAiOjQwfQ==" + }, + { + "name": "world", + "base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + ], + "envelope": { + "base64": "RkxZU0VTUzEBAAAAIAAAAAAIAAAFAAAAIAgAAAAAAAB7ImFnZW50cyI6W3siYWdlbnRJZCI6ImZseS1hIiwiYnJhaW5UaWNrcyI6IjI1MzQiLCJkYXRhc2V0RGlnZXN0IjoiNmMwYWYxZjA3ODRlZjYzYTM5M2VlNzdkNjE0ZTgyNDZjNjI1MDUxMzYwZjNmMWE0ODgzODM3NGM1ZDM1NWI1MiIsIm1vZGVsVmVyc2lvbiI6ImxpZi0xbXMtZjY0LXYyIiwicGF5bG9hZCI6ImFnZW50LWZseS1hIiwicGxhc3RpY2l0eVZlcnNpb24iOiJmbHkta2MtbWJvbi1yc3RkcC12MiIsInByb2ZpbGVEaWdlc3QiOiIxOTAwZWFiNmMwMjg0ODNkNzEyNjU5OWVlNmY1MGRlMGQyNzkwN2I1YzY1ZmE5MDUyNDU4MGI0YjBmOTg1MmIwIiwicmVtYWluZGVyIjp7ImRlbm9taW5hdG9yIjoiMyIsIm51bWVyYXRvciI6IjEwMDAwMDAifSwic2VlZCI6LTE4NDk0NjA2M31dLCJjaGVja3BvaW50SWQiOiJja3B0LTEiLCJjb21wYXRpYmlsaXR5Ijp7ImJhY2tlbmREaWdlc3QiOiIxMGUwOGE0MTllODUwZWJhMWViYmExOGZkZDI4ZWI3ZWMxYjdlOGJhYTliY2MzYjk3M2UyYjg4OTFlYzcyNmJlIiwiY29udGVudERpZ2VzdCI6ImVkNzAwMmI0MzllOWFjODQ1ZjIyMzU3ZDgyMmJhYzE0NDQ3MzBmYmRiNjAxNmQzZWM5NDMyMjk3YjllYzlmNzMiLCJjb250cm9sbGVyRGlnZXN0IjoiYzE0NzIxMzViMTRjNzdjOGJlZjk4ZTczZjcwMjA4MzI1ZmEwZGNmMWU2YmQ2NjhhZTliMzFhOWNlYTI5NWZlNyIsInBhcnNlckRpZ2VzdCI6ImIxN2Q0NTEyMTE1MDkyOGYyMTQ2YWY0OWUxOTVlZmYxZWVmNWQ2NzMyNWJlMjczYTczM2ZiNzRhY2FkYWEzNDIiLCJwYXRjaERpZ2VzdCI6ImE0ODk1ZWI0NGFmYzMzNmZlY2JiYTZlNTIwY2Q2N2UxNzhkYWNlMDI3NjY1NWQxMDJmY2VmZmE4ZTVmNzA1NzAiLCJzdGF0ZUZvcm1hdElkIjoiZmx5c2Vzcy0xIn0sImNvbXBvc2l0aW9uRGlnZXN0IjoiNzMwZDcyNWM4YTU5ZDNhNzMwM2RlZjJiZWQwNDFhNTc3ZWRiNDI1NWFhYmQ0ODg5Y2UxMjkxODMxMWQ5NTJmMCIsImNvb3JkaW5hdG9yIjp7ImFkbWlzc2lvblN0YXRlIjpudWxsLCJldmVudFdhdGVybWFya3MiOnsibGFzdEV2ZW50SWQiOiJldnQtMSIsImxhc3RPcmRpbmFsIjoiNyJ9LCJleGVjdXRvclN0YXRlIjpbeyJhZ2VudElkIjoiZmx5LWEiLCJwYXlsb2FkIjoiZXhlY3V0b3ItZmx5LWEifV0sInByaW9ySW5zcGVjdGlvbiI6InByaW9yLWluc3BlY3Rpb24iLCJ0YXNrTGVkZ2VyIjoidGFzay1sZWRnZXIifSwiZW52ZWxvcGVWZXJzaW9uIjoxLCJlcGlzb2RlSWQiOiJlcGlzb2RlLTEiLCJoZWxwZXJTdGF0ZSI6W10sInBheWxvYWRzIjpbeyJieXRlTGVuZ3RoIjoiMTciLCJkaWdlc3QiOiIxMzIxZGZmYjBjZGM2ZjkwOTJjYmY3ZmEyYTVmYzY4YmJlZDEyYzk5M2Q1YWQzOTgyNjQwMTI4MTBjZTliZjkzIiwibmFtZSI6ImFnZW50LWZseS1hIn0seyJieXRlTGVuZ3RoIjoiMTQiLCJkaWdlc3QiOiIzYWVlNjBkZjdlMjllZmViYTdmNWY5OWZjNTg2NzY0N2IzNmFlYmZmMWQ1ZDNjODM4ZGJmZjMyMzEyMmU2NDYyIiwibmFtZSI6ImV4ZWN1dG9yLWZseS1hIn0seyJieXRlTGVuZ3RoIjoiMTEiLCJkaWdlc3QiOiI0MGIwMGVkMmJiYmE5MDFkNjgyMDVmZjcxYjA0YTQ0YjllZTUzYzUxY2IzMTA5YWEyY2VhYTQ0ZjFjNDU3MjdlIiwibmFtZSI6InRhc2stbGVkZ2VyIn0seyJieXRlTGVuZ3RoIjoiMTAiLCJkaWdlc3QiOiIyYzEzYjdiNGQ5YTk5MTY4MDFhYjkxOTFjMzE0ZjMxYjA0NWU5YjljNWI2NjlhNmMwNDc0ZjAyMTdlZjc1YmY1IiwibmFtZSI6InByaW9yLWluc3BlY3Rpb24ifSx7ImJ5dGVMZW5ndGgiOiI2NCIsImRpZ2VzdCI6ImY1YTVmZDQyZDE2YTIwMzAyNzk4ZWY2ZWQzMDk5NzliNDMwMDNkMjMyMGQ5ZjBlOGVhOTgzMWE5Mjc1OWZiNGIiLCJuYW1lIjoid29ybGQifV0sInBvcnRNYXAiOlt7ImFnZW50SWQiOiJmbHktYSIsInBvcnRJZCI6InBvcnQtMSJ9XSwic2NoZWR1bGVySWQiOiJsb2Nrc3RlcC12MSIsInNvdXJjZVNjb3BlIjp7ImVwb2NoIjoiZXBvY2gtMSIsInNlc3Npb25JZCI6ImRlbW8iLCJzdGVwIjoiNDIifSwid29ybGRUaW1lIjp7ImRlbm9taW5hdG9yIjoiMSIsIm51bWVyYXRvciI6IjcwMDAwMDAwMCJ9fWFnZW50LWZseS1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQCgAAAAAAABEAAAAAAAAAEyHf+wzcb5CSy/f6Kl/Gi77RLJk9WtOYJkASgQzpv5NleGVjdXRvci1mbHktYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAoAAAAAAAAOAAAAAAAAADruYN9+Ke/rp/X5n8WGdkezauv/HV08g42/8yMSLmRidGFzay1sZWRnZXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgKAAAAAAAACwAAAAAAAABAsA7Su7qQHWggX/cbBKRLnuU8UcsxCaos6qRPHEVyfnByaW9yLWluc3BlY3Rpb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACICgAAAAAAAAoAAAAAAAAALBO3tNmpkWgBq5GRwxTzGwRem5xbZppsBHTwIX73W/V3b3JsZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmAoAAAAAAABAAAAAAAAAAPWl/ULRaiAwJ5jvbtMJl5tDAD0jINnw6OqYMaknWftLYWdlbnQgc3RhdGUgYnl0ZXMAAAAAAAAAZXhlY3V0b3Igc3RhdGUAAHsicmFuayI6MTB9AAAAAAB7Im1hcCI6NDB9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgLAAAAAAAAq++fEx+FvDZho/eB4imbENN4HZrGNC2OCAsI7/gp9r5GTFlTRVNTRg==", + "byteLength": 2824, + "layout": { + "headerBytes": 32, + "manifestOffset": "32", + "manifestBytes": 2048, + "tableOffset": "2080", + "tableEntryBytes": 112, + "entries": [ + { + "name": "agent-fly-a", + "offset": "2640", + "byteLength": "17", + "digest": "1321dffb0cdc6f9092cbf7fa2a5fc68bbed12c993d5ad398264012810ce9bf93" + }, + { + "name": "executor-fly-a", + "offset": "2664", + "byteLength": "14", + "digest": "3aee60df7e29efeba7f5f99fc5867647b36aebff1d5d3c838dbff323122e6462" + }, + { + "name": "task-ledger", + "offset": "2680", + "byteLength": "11", + "digest": "40b00ed2bbba901d68205ff71b04a44b9ee53c51cb3109aa2ceaa44f1c45727e" + }, + { + "name": "prior-inspection", + "offset": "2696", + "byteLength": "10", + "digest": "2c13b7b4d9a9916801ab9191c314f31b045e9b9c5b669a6c0474f0217ef75bf5" + }, + { + "name": "world", + "offset": "2712", + "byteLength": "64", + "digest": "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + } + ], + "footerOffset": "2776", + "footerBytes": 48, + "totalBytes": "2824" + } + }, + "corruption": [ + { + "name": "a flipped magic byte", + "offset": 0, + "reason": "wrong magic" + }, + { + "name": "an unsupported version", + "offset": 8, + "reason": "unsupported version" + }, + { + "name": "a flipped manifest byte", + "offset": 32, + "reason": "the footer digest covers the manifest" + }, + { + "name": "a flipped payload byte", + "offset": 2640, + "reason": "every payload carries its own digest" + }, + { + "name": "a flipped footer digest byte", + "offset": 2784, + "reason": "the footer digest must match the contents" + }, + { + "name": "a flipped footer magic byte", + "offset": 2816, + "reason": "a truncated file cannot look complete" + } + ] +} diff --git a/services/flysim/crates/fly-session-types/fixtures/contract-digest.json b/services/flysim/crates/fly-session-types/fixtures/contract-digest.json new file mode 100644 index 0000000..bae6c95 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/contract-digest.json @@ -0,0 +1,9 @@ +{ + "description": "contractDigest is the SHA-256 of the canonical schema set in schema-set.json.", + "contractDigest": "7932aef30c4d2d16e428081affc4e0ad187987f5b361138d553e54fd7f843b50", + "schemaSetVersion": 1, + "schemaSetBytes": 26685, + "types": 53, + "enums": 11, + "limits": 25 +} diff --git a/services/flysim/crates/fly-session-types/fixtures/descriptor-checks.json b/services/flysim/crates/fly-session-types/fixtures/descriptor-checks.json new file mode 100644 index 0000000..c536619 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/descriptor-checks.json @@ -0,0 +1,2085 @@ +{ + "description": "Rules that need a descriptor in hand: complete port batches, the observation delay rule, byte shapes and descriptor agreement.", + "descriptor": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "delayedDescriptor": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 2 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "sessionDescriptor": { + "sessionId": "demo", + "revision": "7", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "schedulerId": "lockstep-v1", + "environment": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "taskSchema": { + "id": "pokemon.task.v1", + "version": 1, + "digest": "e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4" + }, + "agents": [ + { + "agentId": "fly-a", + "portId": "port-1", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + } + ], + "assets": [] + }, + "cases": [ + { + "name": "a complete port control", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "expect": "accept", + "reason": "" + }, + { + "name": "a port control at both analog limits", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": -1.0 + }, + { + "id": "trigger", + "value": 1.0 + } + ] + }, + "expect": "accept", + "reason": "the limits are inclusive" + }, + { + "name": "a port control one step past a bipolar limit", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": -1.0000000000000002 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "expect": "reject", + "reason": "out of range is refused, never clamped" + }, + { + "name": "a port control past a unit limit", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 1.5 + } + ] + }, + "expect": "reject", + "reason": "out of range is refused, never clamped" + }, + { + "name": "a port control with a missing button", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": false + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "expect": "reject", + "reason": "every declared button must be present" + }, + { + "name": "a port control with an extra button", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": false + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + }, + { + "id": "turbo", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "expect": "reject", + "reason": "no extra controls" + }, + { + "name": "a port control with the buttons in another order", + "kind": "portControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "right", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "b", + "down": false + }, + { + "id": "a", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "expect": "reject", + "reason": "descriptor order is part of the contract" + }, + { + "name": "a complete batch", + "kind": "advanceControls", + "value": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ], + "expect": "accept", + "reason": "" + }, + { + "name": "a batch that omits a declared port", + "kind": "advanceControls", + "value": [], + "expect": "reject", + "reason": "uncontrolled ports are configured neutral, not omitted" + }, + { + "name": "a batch with the same port twice", + "kind": "advanceControls", + "value": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ], + "expect": "reject", + "reason": "reject duplicates and missing ports" + }, + { + "name": "a batch naming an undeclared port", + "kind": "advanceControls", + "value": [ + { + "portId": "port-9", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ], + "expect": "reject", + "reason": "all ids must match exactly" + }, + { + "name": "sensory input at its boundary with no declared delay", + "kind": "sensoryInput", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "accept", + "reason": "" + }, + { + "name": "sensory input one boundary stale with no declared delay", + "kind": "sensoryInput", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "reject", + "reason": "missing or extra delay is a step failure, not an arbitrary latest frame" + }, + { + "name": "sensory input for an undeclared view", + "kind": "sensoryInput", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "hud", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "reject", + "reason": "the environment declares its views" + }, + { + "name": "sensory input with the wrong frame length", + "kind": "sensoryInput", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-short", + "generation": "1", + "byteLength": "92156", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "reject", + "reason": "artifact length equals rowStride x height" + }, + { + "name": "sensory input honouring a two step pipeline delay", + "kind": "sensoryInputDelayed", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "40", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "accept", + "reason": "producedStep is max(0, boundary - observationDelaySteps)" + }, + { + "name": "sensory input ignoring a two step pipeline delay", + "kind": "sensoryInputDelayed", + "value": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "reject", + "reason": "a fresher frame than the declared pipeline is also a failure" + }, + { + "name": "bootstrap repeating boundary zero under a delay", + "kind": "sensoryInputDelayed", + "value": { + "boundary": "1", + "views": [ + { + "viewId": "screen", + "producedStep": "0", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "expect": "accept", + "reason": "max(0, 1 - 2) is 0: bootstrap may repeat O[0]" + }, + { + "name": "a world observation", + "kind": "worldObservation", + "value": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + }, + "expect": "accept", + "reason": "" + }, + { + "name": "a world observation with a mismatched audio length", + "kind": "worldObservation", + "value": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "0", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-short", + "generation": "1", + "byteLength": "6396", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + }, + "expect": "reject", + "reason": "artifact length is sampleFrames x channels x 4" + }, + { + "name": "a world observation with another inspection schema", + "kind": "worldObservation", + "value": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "other.inspection.v1", + "version": 1, + "digest": "8d6ebfb7599aaa93eeeb14acb64a6842e700174be22a5c6a5a5994ee9a54229b" + }, + "value": {} + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + }, + "expect": "reject", + "reason": "inspection uses the descriptor's schema" + }, + { + "name": "a step result that advances world time by one step duration", + "kind": "stepResult", + "value": { + "batchId": "batch-41", + "appliedFromStep": "41", + "nextStep": "42", + "appliedControlsDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "observation": { + "boundary": "42", + "worldTime": { + "numerator": "100000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "expect": "accept", + "reason": "two steps of 50000000/3 ns" + }, + { + "name": "a step result that advances world time by two step durations", + "kind": "stepResult", + "value": { + "batchId": "batch-41", + "appliedFromStep": "41", + "nextStep": "42", + "appliedControlsDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "observation": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "1" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "expect": "reject", + "reason": "an emulator cannot hide two framework steps behind one result" + }, + { + "name": "a snapshot that agrees with its descriptor", + "kind": "snapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "expect": "accept", + "reason": "" + }, + { + "name": "a snapshot naming an agent outside the composition", + "kind": "snapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-z", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "expect": "reject", + "reason": "descriptor and snapshot must agree" + }, + { + "name": "a snapshot driving another agent's port", + "kind": "snapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-2", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "expect": "reject", + "reason": "the coordinator assigns the port, and it is the assigned one" + }, + { + "name": "a snapshot with telemetry roles in another order", + "kind": "snapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "mbon", + "hz": 0.0 + }, + { + "roleId": "kenyon", + "hz": 3.25 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "expect": "reject", + "reason": "rates are in profile-defined order" + }, + { + "name": "a snapshot for another descriptor revision", + "kind": "snapshot", + "value": { + "descriptorRevision": "6", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "expect": "reject", + "reason": "descriptor/index mismatch is visible" + } + ], + "stepResultPrevious": { + "boundary": "41", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "32800", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/generated.json b/services/flysim/crates/fly-session-types/fixtures/generated.json new file mode 100644 index 0000000..9192e73 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/generated.json @@ -0,0 +1,74 @@ +{ + "description": "Boundary cases both languages build from a recipe, because the payload is too large to store.", + "padSchema": { + "id": "pad.v1", + "version": 1, + "digest": "018689202f154300eeda48ccee8cc021c36672df03f7404097613f5898fdfdcc" + }, + "cases": [ + { + "name": "typed value exactly at the 32 KiB cap", + "kind": "padded-typed-value", + "padCharacters": 32635, + "expect": "accept", + "reason": "32768 bytes of canonical JSON is the limit, not one byte less" + }, + { + "name": "typed value one byte over the cap", + "kind": "padded-typed-value", + "padCharacters": 32636, + "expect": "reject", + "reason": "a TypedValue is at most 32 KiB of canonical JSON" + }, + { + "name": "request that fills the 64 KiB envelope exactly", + "kind": "padded-request", + "padCharacters": 60000, + "expect": "accept", + "reason": "65536 bytes including the envelope wrapper is admissible", + "envelopeTotal": 65536 + }, + { + "name": "request one byte past the envelope ceiling", + "kind": "padded-request", + "padCharacters": 60000, + "expect": "reject", + "reason": "the complete envelope must fit Flybus's 64-KiB maximum", + "envelopeTotal": 65537 + }, + { + "name": "error message of 512 code points", + "kind": "error-message", + "codePoints": 512, + "expect": "accept", + "reason": "messages are <= 512 code points" + }, + { + "name": "error message of 513 code points", + "kind": "error-message", + "codePoints": 513, + "expect": "reject", + "reason": "messages are <= 512 code points" + }, + { + "name": "error message of 512 astral code points", + "kind": "error-message-astral", + "codePoints": 512, + "expect": "accept", + "reason": "the bound counts code points, not UTF-16 units or bytes" + }, + { + "name": "error message of 513 astral code points", + "kind": "error-message-astral", + "codePoints": 513, + "expect": "reject", + "reason": "the bound counts code points, not UTF-16 units or bytes" + } + ], + "recipes": { + "padded-typed-value": "a TypedValue whose schema is padSchema and whose value is {\"pad\": 'a' characters}; validate it", + "padded-request": "a SessionRpcRequest req-1 with scope null and params {\"pad\": 'a' characters}; canonicalize it, then require the envelope to fit with an overhead of envelopeTotal minus that canonical length", + "error-message": "a SessionRpcFailure with code INTERNAL, mutation unknown and a message of 'x' characters", + "error-message-astral": "the same failure with a message of repetitions of U+10400, one code point and two UTF-16 units each" + } +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/identities.json b/services/flysim/crates/fly-session-types/fixtures/identities.json new file mode 100644 index 0000000..be7b38f --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/identities.json @@ -0,0 +1,111 @@ +{ + "description": "Bus callId, domain requestId, artifact identity and delivery/hold owner tokens are four types, not four spellings of one string.", + "cases": [ + { + "text": "call-0", + "busCallId": true, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "call-102", + "busCallId": true, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "req-41", + "busCallId": false, + "domainRequestId": true, + "ownerToken": null + }, + { + "text": "dlv-7", + "busCallId": false, + "domainRequestId": false, + "ownerToken": "delivery" + }, + { + "text": "own-9", + "busCallId": false, + "domainRequestId": false, + "ownerToken": "hold" + }, + { + "text": "req-041", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "call-", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "call", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "callid-1", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "request-1", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "REQ-1", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "dlv-07", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "sub-1", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + }, + { + "text": "svc-1", + "busCallId": false, + "domainRequestId": false, + "ownerToken": null + } + ], + "artifact": { + "ref": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + }, + "identity": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1" + }, + "reason": "the identity is the naming half of an ArtifactRef; byteLength, contentType and digest are not identity, and an AssetRef is not an artifact at all" + }, + "asset": { + "id": "profile-fly-a", + "digest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "byteLength": "4096", + "format": "flyprofile" + } +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/invalid.json b/services/flysim/crates/fly-session-types/fixtures/invalid.json new file mode 100644 index 0000000..cb5fa10 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/invalid.json @@ -0,0 +1,3900 @@ +{ + "description": "Payloads every implementation must refuse in its validate step.", + "cases": [ + { + "name": "scope with a non-canonical step", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "041" + }, + "reason": "U64 has no leading zeros" + }, + { + "name": "scope one past the U64 maximum", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "18446744073709551616" + }, + "reason": "U64 overflow" + }, + { + "name": "scope with an uppercase session id", + "type": "Scope", + "value": { + "sessionId": "Demo", + "epoch": "epoch-1", + "step": "0" + }, + "reason": "Id is lowercase" + }, + { + "name": "scope with a numeric step", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": 41 + }, + "reason": "counters are decimal strings" + }, + { + "name": "scope missing its epoch", + "type": "Scope", + "value": { + "sessionId": "demo", + "step": "0" + }, + "reason": "every field is required" + }, + { + "name": "scope with an unknown field", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41", + "boundary": "41" + }, + "reason": "unknown fields are refused" + }, + { + "name": "scope with an empty session id", + "type": "Scope", + "value": { + "sessionId": "", + "epoch": "epoch-1", + "step": "0" + }, + "reason": "Id is 1..=64 characters" + }, + { + "name": "scope with a 65 character session id", + "type": "Scope", + "value": { + "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "epoch": "epoch-1", + "step": "0" + }, + "reason": "Id is 1..=64 characters" + }, + { + "name": "rational with a zero denominator", + "type": "RationalNs", + "value": { + "numerator": "1", + "denominator": "0" + }, + "reason": "denominators are positive" + }, + { + "name": "rational zero in another form", + "type": "RationalNs", + "value": { + "numerator": "0", + "denominator": "2" + }, + "reason": "zero is encoded 0/1" + }, + { + "name": "rational that is not reduced", + "type": "RationalNs", + "value": { + "numerator": "2", + "denominator": "4" + }, + "reason": "fractions are reduced" + }, + { + "name": "rational with a numeric numerator", + "type": "RationalNs", + "value": { + "numerator": 1, + "denominator": "2" + }, + "reason": "U64 is a decimal string" + }, + { + "name": "schema reference at version zero", + "type": "SchemaRef", + "value": { + "id": "a.v1", + "version": 0, + "digest": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" + }, + "reason": "version is 1..=65535" + }, + { + "name": "schema reference past the version ceiling", + "type": "SchemaRef", + "value": { + "id": "a.v1", + "version": 65536, + "digest": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" + }, + "reason": "version is 1..=65535" + }, + { + "name": "schema reference with an uppercase digest", + "type": "SchemaRef", + "value": { + "id": "a.v1", + "version": 1, + "digest": "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB" + }, + "reason": "Digest is lowercase hex" + }, + { + "name": "schema reference with a short digest", + "type": "SchemaRef", + "value": { + "id": "a.v1", + "version": 1, + "digest": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48b" + }, + "reason": "Digest is 64 hex digits" + }, + { + "name": "typed value whose value is not an object", + "type": "TypedValue", + "value": { + "schema": { + "id": "a.v1", + "version": 1, + "digest": "bba7951c04664cd49bb6eed1da6dc1a4e17e989e787bf2e72f5933a5c6171372" + }, + "value": [ + 1, + 2 + ] + }, + "reason": "typed state is an object" + }, + { + "name": "typed value with a missing schema", + "type": "TypedValue", + "value": { + "value": {} + }, + "reason": "every field is required" + }, + { + "name": "stimulus with a zero duration", + "type": "Stimulus", + "value": { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 0.0 + }, + "reason": "durationMs is > 0" + }, + { + "name": "stimulus with a negative duration", + "type": "Stimulus", + "value": { + "id": "stim-1", + "kindId": "sugar", + "durationMs": -1.0 + }, + "reason": "durationMs is > 0" + }, + { + "name": "stimulus with a string duration", + "type": "Stimulus", + "value": { + "id": "stim-1", + "kindId": "sugar", + "durationMs": "50" + }, + "reason": "durationMs is a JSON number" + }, + { + "name": "reward with a string value", + "type": "Reward", + "value": { + "eventId": "evt-1", + "ruleId": "badge", + "value": "1.5" + }, + "reason": "rewards are finite JSON numbers" + }, + { + "name": "telemetry with a negative rate", + "type": "AgentTelemetry", + "value": { + "brainTicks": "17", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": -0.5 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "reason": "rates are nonnegative" + }, + { + "name": "telemetry with a duplicate role", + "type": "AgentTelemetry", + "value": { + "brainTicks": "17", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 1.0 + }, + { + "roleId": "kenyon", + "hz": 2.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "reason": "rates are unique by role id" + }, + { + "name": "telemetry with 65 roles", + "type": "AgentTelemetry", + "value": { + "brainTicks": "17", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "role-0", + "hz": 1.0 + }, + { + "roleId": "role-1", + "hz": 1.0 + }, + { + "roleId": "role-2", + "hz": 1.0 + }, + { + "roleId": "role-3", + "hz": 1.0 + }, + { + "roleId": "role-4", + "hz": 1.0 + }, + { + "roleId": "role-5", + "hz": 1.0 + }, + { + "roleId": "role-6", + "hz": 1.0 + }, + { + "roleId": "role-7", + "hz": 1.0 + }, + { + "roleId": "role-8", + "hz": 1.0 + }, + { + "roleId": "role-9", + "hz": 1.0 + }, + { + "roleId": "role-10", + "hz": 1.0 + }, + { + "roleId": "role-11", + "hz": 1.0 + }, + { + "roleId": "role-12", + "hz": 1.0 + }, + { + "roleId": "role-13", + "hz": 1.0 + }, + { + "roleId": "role-14", + "hz": 1.0 + }, + { + "roleId": "role-15", + "hz": 1.0 + }, + { + "roleId": "role-16", + "hz": 1.0 + }, + { + "roleId": "role-17", + "hz": 1.0 + }, + { + "roleId": "role-18", + "hz": 1.0 + }, + { + "roleId": "role-19", + "hz": 1.0 + }, + { + "roleId": "role-20", + "hz": 1.0 + }, + { + "roleId": "role-21", + "hz": 1.0 + }, + { + "roleId": "role-22", + "hz": 1.0 + }, + { + "roleId": "role-23", + "hz": 1.0 + }, + { + "roleId": "role-24", + "hz": 1.0 + }, + { + "roleId": "role-25", + "hz": 1.0 + }, + { + "roleId": "role-26", + "hz": 1.0 + }, + { + "roleId": "role-27", + "hz": 1.0 + }, + { + "roleId": "role-28", + "hz": 1.0 + }, + { + "roleId": "role-29", + "hz": 1.0 + }, + { + "roleId": "role-30", + "hz": 1.0 + }, + { + "roleId": "role-31", + "hz": 1.0 + }, + { + "roleId": "role-32", + "hz": 1.0 + }, + { + "roleId": "role-33", + "hz": 1.0 + }, + { + "roleId": "role-34", + "hz": 1.0 + }, + { + "roleId": "role-35", + "hz": 1.0 + }, + { + "roleId": "role-36", + "hz": 1.0 + }, + { + "roleId": "role-37", + "hz": 1.0 + }, + { + "roleId": "role-38", + "hz": 1.0 + }, + { + "roleId": "role-39", + "hz": 1.0 + }, + { + "roleId": "role-40", + "hz": 1.0 + }, + { + "roleId": "role-41", + "hz": 1.0 + }, + { + "roleId": "role-42", + "hz": 1.0 + }, + { + "roleId": "role-43", + "hz": 1.0 + }, + { + "roleId": "role-44", + "hz": 1.0 + }, + { + "roleId": "role-45", + "hz": 1.0 + }, + { + "roleId": "role-46", + "hz": 1.0 + }, + { + "roleId": "role-47", + "hz": 1.0 + }, + { + "roleId": "role-48", + "hz": 1.0 + }, + { + "roleId": "role-49", + "hz": 1.0 + }, + { + "roleId": "role-50", + "hz": 1.0 + }, + { + "roleId": "role-51", + "hz": 1.0 + }, + { + "roleId": "role-52", + "hz": 1.0 + }, + { + "roleId": "role-53", + "hz": 1.0 + }, + { + "roleId": "role-54", + "hz": 1.0 + }, + { + "roleId": "role-55", + "hz": 1.0 + }, + { + "roleId": "role-56", + "hz": 1.0 + }, + { + "roleId": "role-57", + "hz": 1.0 + }, + { + "roleId": "role-58", + "hz": 1.0 + }, + { + "roleId": "role-59", + "hz": 1.0 + }, + { + "roleId": "role-60", + "hz": 1.0 + }, + { + "roleId": "role-61", + "hz": 1.0 + }, + { + "roleId": "role-62", + "hz": 1.0 + }, + { + "roleId": "role-63", + "hz": 1.0 + }, + { + "roleId": "role-64", + "hz": 1.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "reason": "at most 64 rate roles" + }, + { + "name": "telemetry claiming more changes than updates", + "type": "AgentTelemetry", + "value": { + "brainTicks": "17", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "1", + "changed": "2", + "signal": 0.0 + } + }, + "reason": "changed <= updates" + }, + { + "name": "sensory input with nine views", + "type": "SensoryInput", + "value": { + "boundary": "1", + "views": [ + { + "viewId": "view-0", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-0", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-1", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-2", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-2", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-3", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-3", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-4", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-4", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-5", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-5", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-6", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-6", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-7", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-7", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "view-8", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-8", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "reason": "at most 8 views per sensory input" + }, + { + "name": "sensory input with a duplicate view", + "type": "SensoryInput", + "value": { + "boundary": "1", + "views": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "reason": "views are unique by view id" + }, + { + "name": "sensory input observing the future", + "type": "SensoryInput", + "value": { + "boundary": "1", + "views": [ + { + "viewId": "screen", + "producedStep": "2", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "reason": "a view cannot be produced after its boundary" + }, + { + "name": "view descriptor with a zero width", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 0, + "height": 144, + "format": "rgba8", + "rowStride": 0, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "reason": "dimensions are 1..=4096" + }, + { + "name": "view descriptor past the dimension ceiling", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 4097, + "height": 144, + "format": "rgba8", + "rowStride": 16388, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "reason": "dimensions are 1..=4096" + }, + { + "name": "view descriptor with a padded row", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 644, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "reason": "rowStride is exactly 4 x width" + }, + { + "name": "view descriptor with a zero pixel aspect", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 0, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "reason": "pixel aspect parts are positive" + }, + { + "name": "view descriptor with a nine step delay", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 9 + }, + "reason": "observationDelaySteps is 0..=8" + }, + { + "name": "view descriptor in another format", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgb8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "reason": "v1 has one pixel format" + }, + { + "name": "audio descriptor below the sample rate floor", + "type": "AudioDescriptor", + "value": { + "streamId": "mix", + "sampleRate": 7999, + "channels": 2, + "format": "f32le-interleaved" + }, + "reason": "sampleRate is 8000..=192000" + }, + { + "name": "audio descriptor with nine channels", + "type": "AudioDescriptor", + "value": { + "streamId": "mix", + "sampleRate": 48000, + "channels": 9, + "format": "f32le-interleaved" + }, + "reason": "channels are 1..=8" + }, + { + "name": "audio chunk past the frame ceiling", + "type": "AudioRef", + "value": { + "streamId": "mix", + "firstSample": "0", + "sampleFrames": 192001, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "1536008", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + }, + "reason": "sampleFrames is 0..=192000" + }, + { + "name": "controller schema with a duplicate button", + "type": "ControllerSchema", + "value": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "a" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + }, + "reason": "buttons are unique" + }, + { + "name": "controller schema with 33 buttons", + "type": "ControllerSchema", + "value": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "b0", + "b1", + "b2", + "b3", + "b4", + "b5", + "b6", + "b7", + "b8", + "b9", + "b10", + "b11", + "b12", + "b13", + "b14", + "b15", + "b16", + "b17", + "b18", + "b19", + "b20", + "b21", + "b22", + "b23", + "b24", + "b25", + "b26", + "b27", + "b28", + "b29", + "b30", + "b31", + "b32" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + }, + "reason": "at most 32 buttons" + }, + { + "name": "controller schema with 17 axes", + "type": "ControllerSchema", + "value": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "ax0", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax1", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax2", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax3", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax4", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax5", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax6", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax7", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax8", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax9", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax10", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax11", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax12", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax13", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax14", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax15", + "range": "unit", + "neutral": 0.0 + }, + { + "id": "ax16", + "range": "unit", + "neutral": 0.0 + } + ] + }, + "reason": "at most 16 axes" + }, + { + "name": "controller schema with an out of range neutral", + "type": "ControllerSchema", + "value": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "trigger", + "range": "unit", + "neutral": -0.5 + } + ] + }, + "reason": "neutral lies in range" + }, + { + "name": "port control with a non-finite axis", + "type": "PortControl", + "value": { + "portId": "port-1", + "buttons": [], + "axes": [ + { + "id": "stick-x", + "value": "NaN" + } + ] + }, + "reason": "axis values are finite numbers" + }, + { + "name": "port control with a duplicate button", + "type": "PortControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "a", + "down": false + } + ], + "axes": [] + }, + "reason": "buttons are unique" + }, + { + "name": "environment descriptor with five ports", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-2", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-3", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-4", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-5", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "reason": "the first composition allows four ports" + }, + { + "name": "environment descriptor with a duplicate port", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "reason": "ports are unique" + }, + { + "name": "environment descriptor with a zero step duration", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "0", + "denominator": "1" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "reason": "durations are positive" + }, + { + "name": "environment descriptor with an unreduced step duration", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "100000000", + "denominator": "6" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "reason": "fractions are reduced" + }, + { + "name": "environment descriptor with nine views", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "view-0", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-1", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-2", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-3", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-4", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-5", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-6", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-7", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + { + "viewId": "view-8", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "reason": "at most 8 views" + }, + { + "name": "environment descriptor with an unverified recovery mode", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "best-effort", + "determinism": "fixed-build" + }, + "reason": "recovery is a closed enum" + }, + { + "name": "environment initialize result at boundary one", + "type": "EnvironmentInitializeResult", + "value": { + "descriptor": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "observation": { + "boundary": "1", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "800", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "reason": "initialization returns boundary 0" + }, + { + "name": "environment initialize result with a nonzero world time", + "type": "EnvironmentInitializeResult", + "value": { + "descriptor": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "observation": { + "boundary": "0", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "0", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [], + "audio": [] + } + }, + "reason": "the initial world time is zero" + }, + { + "name": "port bindings with a duplicate port", + "type": "EnvironmentInitializeParams", + "value": { + "backendConfig": { + "id": "backend-gb", + "digest": "b86130f294ef46c5343a505946e6e0507f061d7a1bcf46f09c6d42e1aeb9a2a1", + "byteLength": "512", + "format": "flybackend" + }, + "taskConfig": { + "id": "task-escape", + "digest": "b48eb1e3b6f871c6384b86387e59d4e3b6c69245168011968d5982204c656ac6", + "byteLength": "256", + "format": "flytask" + }, + "episodeId": "episode-1", + "portBindings": [ + { + "portId": "port-1", + "agentId": "fly-a" + }, + { + "portId": "port-1", + "agentId": "fly-b" + } + ] + }, + "reason": "port bindings are unique by port" + }, + { + "name": "port bindings with a duplicate agent", + "type": "EnvironmentInitializeParams", + "value": { + "backendConfig": { + "id": "backend-gb", + "digest": "b86130f294ef46c5343a505946e6e0507f061d7a1bcf46f09c6d42e1aeb9a2a1", + "byteLength": "512", + "format": "flybackend" + }, + "taskConfig": { + "id": "task-escape", + "digest": "b48eb1e3b6f871c6384b86387e59d4e3b6c69245168011968d5982204c656ac6", + "byteLength": "256", + "format": "flytask" + }, + "episodeId": "episode-1", + "portBindings": [ + { + "portId": "port-1", + "agentId": "fly-a" + }, + { + "portId": "port-2", + "agentId": "fly-a" + } + ] + }, + "reason": "port bindings are unique by agent" + }, + { + "name": "advance params with a duplicate port", + "type": "AdvanceParams", + "value": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + }, + "reason": "one control per port" + }, + { + "name": "advance params with five ports", + "type": "AdvanceParams", + "value": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-2", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-3", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-4", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-5", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + }, + "reason": "at most four ports" + }, + { + "name": "advance params with no controls", + "type": "AdvanceParams", + "value": { + "batchId": "batch-41", + "controls": [] + }, + "reason": "an advance applies a complete batch" + }, + { + "name": "step result skipping a step", + "type": "StepResult", + "value": { + "batchId": "batch-41", + "appliedFromStep": "41", + "nextStep": "43", + "appliedControlsDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "observation": { + "boundary": "43", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "43", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "43", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "34400", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "reason": "one result is exactly one step" + }, + { + "name": "step result whose observation is the old boundary", + "type": "StepResult", + "value": { + "batchId": "batch-41", + "appliedFromStep": "41", + "nextStep": "42", + "appliedControlsDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "observation": { + "boundary": "41", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "32800", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "reason": "the observation boundary is nextStep" + }, + { + "name": "prepared decision advancing more ticks than the brain has", + "type": "PreparedDecision", + "value": { + "agentId": "fly-a", + "ticksAdvanced": "18", + "brainTicks": "17", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": {} + } + }, + "reason": "ticksAdvanced <= brainTicks" + }, + { + "name": "agent initialize result at a nonzero committed step", + "type": "AgentInitializeResult", + "value": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "tickDuration": { + "numerator": "1000000", + "denominator": "1" + }, + "warmupTicks": "2500", + "committedStep": "1", + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "telemetry": { + "brainTicks": "2500", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + } + }, + "reason": "initialization establishes Ready(0)" + }, + { + "name": "agent initialize result with a zero tick duration", + "type": "AgentInitializeResult", + "value": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "tickDuration": { + "numerator": "0", + "denominator": "1" + }, + "warmupTicks": "2500", + "committedStep": "0", + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "telemetry": { + "brainTicks": "2500", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + } + }, + "reason": "durations are positive" + }, + { + "name": "hello result for an agent without agent-step-v1", + "type": "HelloResult", + "value": { + "selectedMajor": 1, + "selectedMinor": 0, + "workerId": "fly-a", + "incarnationId": "inc-1", + "role": "agent", + "buildDigest": "44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66", + "contractDigest": "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf", + "capabilities": [ + "checkpoint-v1" + ], + "limits": { + "maxAgents": 4, + "maxPorts": 4 + } + }, + "reason": "an agent must advertise agent-step-v1" + }, + { + "name": "hello result promising five agents", + "type": "HelloResult", + "value": { + "selectedMajor": 1, + "selectedMinor": 0, + "workerId": "fly-a", + "incarnationId": "inc-1", + "role": "agent", + "buildDigest": "44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66", + "contractDigest": "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf", + "capabilities": [ + "agent-step-v1" + ], + "limits": { + "maxAgents": 5, + "maxPorts": 4 + } + }, + "reason": "the first composition allows four agents" + }, + { + "name": "hello result selecting major 2", + "type": "HelloResult", + "value": { + "selectedMajor": 2, + "selectedMinor": 0, + "workerId": "fly-a", + "incarnationId": "inc-1", + "role": "agent", + "buildDigest": "44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66", + "contractDigest": "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf", + "capabilities": [ + "agent-step-v1" + ], + "limits": { + "maxAgents": 4, + "maxPorts": 4 + } + }, + "reason": "v1 selects major 1" + }, + { + "name": "hello params with no supported majors", + "type": "HelloParams", + "value": { + "sessionId": "demo", + "expectedWorkerId": "fly-a", + "role": "agent", + "supportedMajors": [] + }, + "reason": "at least one major" + }, + { + "name": "hello params with an unknown role", + "type": "HelloParams", + "value": { + "sessionId": "demo", + "expectedWorkerId": "fly-a", + "role": "presenter", + "supportedMajors": [ + 1 + ] + }, + "reason": "role is a closed enum" + }, + { + "name": "status result that is uninitialized but scoped", + "type": "StatusResult", + "value": { + "state": "uninitialized", + "currentScope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "activeRequestId": null, + "lastCompletedRequestId": null, + "lastBatchId": null, + "progressCounter": "0" + }, + "reason": "an uninitialized worker has no scope" + }, + { + "name": "status result in an unknown phase", + "type": "StatusResult", + "value": { + "state": "thinking", + "currentScope": null, + "activeRequestId": null, + "lastCompletedRequestId": null, + "lastBatchId": null, + "progressCounter": "0" + }, + "reason": "state is a closed enum" + }, + { + "name": "acknowledge params with no ids", + "type": "AcknowledgeParams", + "value": { + "requestIds": [] + }, + "reason": "1..=16 ids" + }, + { + "name": "acknowledge params with 17 ids", + "type": "AcknowledgeParams", + "value": { + "requestIds": [ + "req-1", + "req-2", + "req-3", + "req-4", + "req-5", + "req-6", + "req-7", + "req-8", + "req-9", + "req-10", + "req-11", + "req-12", + "req-13", + "req-14", + "req-15", + "req-16", + "req-17" + ] + }, + "reason": "1..=16 ids" + }, + { + "name": "acknowledge params with a duplicate id", + "type": "AcknowledgeParams", + "value": { + "requestIds": [ + "req-1", + "req-1" + ] + }, + "reason": "ids are unique" + }, + { + "name": "acknowledge params with a bus call id", + "type": "AcknowledgeParams", + "value": { + "requestIds": [ + "call-1" + ] + }, + "reason": "a bus callId is not a domain requestId" + }, + { + "name": "request whose id is a bus call id", + "type": "SessionRpcRequest", + "value": { + "requestId": "call-41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": {} + }, + "reason": "a bus callId is not a domain requestId" + }, + { + "name": "request whose id has a leading zero", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-041", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": {} + }, + "reason": "the serial is a canonical U64" + }, + { + "name": "request carrying a bus call id in its params", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "callId": "call-2" + } + }, + "reason": "bus identities are never part of a domain body" + }, + { + "name": "request carrying an owner token in its params", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "frame": { + "ownerId": "own-3" + } + } + }, + "reason": "bus identities are never part of a domain body" + }, + { + "name": "request with array params", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": [] + }, + "reason": "params is an object" + }, + { + "name": "failure claiming a mutation for a pre-mutation code", + "type": "SessionRpcFailure", + "value": { + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "error": { + "code": "INVALID_ARGUMENT", + "message": "bad range", + "mutation": "applied" + } + }, + "reason": "INVALID_ARGUMENT is raised before mutation" + }, + { + "name": "failure with an unknown code", + "type": "SessionRpcFailure", + "value": { + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "error": { + "code": "TIMEOUT", + "message": "late", + "mutation": "unknown" + } + }, + "reason": "the code list is closed" + }, + { + "name": "failure with an unknown mutation certainty", + "type": "SessionRpcFailure", + "value": { + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "error": { + "code": "INTERNAL", + "message": "fault", + "mutation": "maybe" + } + }, + "reason": "mutation is none, applied or unknown" + }, + { + "name": "success labelled as an error", + "type": "SessionRpcSuccess", + "value": { + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "result": {} + }, + "reason": "the type discriminant is exact" + }, + { + "name": "capture result without a payload digest", + "type": "CaptureResult", + "value": { + "checkpointId": "ckpt-1", + "boundary": "42", + "compatibilityDigest": "2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d", + "payload": { + "storeId": "store-1", + "artifactId": "payload-1", + "generation": "1", + "byteLength": "1024", + "contentType": "application/octet-stream", + "digest": null + } + }, + "reason": "checkpoint payload digests are mandatory" + }, + { + "name": "stage restore params without a payload digest", + "type": "StageRestoreParams", + "value": { + "checkpointId": "ckpt-1", + "sourceScope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "compatibilityDigest": "2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d", + "payload": { + "storeId": "store-1", + "artifactId": "payload-2", + "generation": "1", + "byteLength": "1024", + "contentType": "application/octet-stream", + "digest": null + } + }, + "reason": "checkpoint payload digests are mandatory" + }, + { + "name": "activate restore result whose observation is another boundary", + "type": "ActivateRestoreResult", + "value": { + "committedStep": "42", + "checkpointId": "ckpt-1", + "observation": { + "boundary": "41", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "32800", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "reason": "the restored observation is the committed boundary" + }, + { + "name": "session descriptor with another scheduler", + "type": "SessionDescriptor", + "value": { + "sessionId": "demo", + "revision": "7", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "schedulerId": "legacy-gameboy-v1", + "environment": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "taskSchema": { + "id": "pokemon.task.v1", + "version": 1, + "digest": "e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4" + }, + "agents": [ + { + "agentId": "fly-a", + "portId": "port-1", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + } + ], + "assets": [] + }, + "reason": "lockstep-v1 is the scheduler of this contract" + }, + { + "name": "session descriptor binding an undeclared port", + "type": "SessionDescriptor", + "value": { + "sessionId": "demo", + "revision": "7", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "schedulerId": "lockstep-v1", + "environment": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "taskSchema": { + "id": "pokemon.task.v1", + "version": 1, + "digest": "e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4" + }, + "agents": [ + { + "agentId": "fly-a", + "portId": "port-9", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + } + ], + "assets": [] + }, + "reason": "an agent's port must be declared by the environment" + }, + { + "name": "session descriptor with five agents", + "type": "SessionDescriptor", + "value": { + "sessionId": "demo", + "revision": "7", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "schedulerId": "lockstep-v1", + "environment": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-2", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-3", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-4", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "taskSchema": { + "id": "pokemon.task.v1", + "version": 1, + "digest": "e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4" + }, + "agents": [ + { + "agentId": "fly-1", + "portId": "port-1", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + }, + { + "agentId": "fly-2", + "portId": "port-2", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + }, + { + "agentId": "fly-3", + "portId": "port-3", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + }, + { + "agentId": "fly-4", + "portId": "port-4", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + }, + { + "agentId": "fly-5", + "portId": "port-5", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + } + ], + "assets": [] + }, + "reason": "the first composition allows four agents" + }, + { + "name": "snapshot with a decision at boundary zero", + "type": "CommittedSnapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "0" + }, + "episodeId": "episode-1", + "sequence": "0", + "worldTime": { + "numerator": "0", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2500", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": null + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 0 + } + }, + "media": { + "views": [], + "audio": [] + }, + "eventIds": [] + }, + "reason": "boundary 0 has no preceding transition" + }, + { + "name": "snapshot without controls after a transition", + "type": "CommittedSnapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": null + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [], + "audio": [] + }, + "eventIds": [] + }, + "reason": "a committed transition has applied controls" + }, + { + "name": "trace whose commit acknowledgment is the old boundary", + "type": "TransitionTrace", + "value": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4", + "committedStep": "41" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [], + "outcomeIds": [], + "eventIds": [], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-1" + } + ], + "advanceRequestId": "req-2", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-3" + } + ], + "busCallIds": [], + "deliveryIds": [] + } + }, + "reason": "a commit acknowledges the transition's next boundary" + } + ] +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/operations.json b/services/flysim/crates/fly-session-types/fixtures/operations.json new file mode 100644 index 0000000..3c35a6a --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/operations.json @@ -0,0 +1,667 @@ +{ + "description": "ipc-v1 section 5: the operation key of a step mutation and the canonical body a duplicate is compared against.", + "keys": [ + { + "name": "Agent.Prepare on fly-a at step 41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "method": "Agent.Prepare", + "workerId": "fly-a", + "digest": "eda73d62bb5d616052997ab5e9d2c4ba5872fdefb44cf706910a18a24d39225f" + }, + { + "name": "Agent.Prepare on fly-b at step 41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "method": "Agent.Prepare", + "workerId": "fly-b", + "digest": "5a7fd15da236f3fd6343698a8cc5c4f14fa7d84683a819d5a28904d90f700c4e" + }, + { + "name": "Agent.Commit on fly-a at step 41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "method": "Agent.Commit", + "workerId": "fly-a", + "digest": "2a89a120e5594895d024b4ffc2a7d225bd27322cfd345ebd78e80ed7b271c680" + }, + { + "name": "Agent.Prepare on fly-a at step 42", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "method": "Agent.Prepare", + "workerId": "fly-a", + "digest": "5bb184cab6c3c2d7567a7979d860a7e9b26ea52bdc92b139053b0a2f81a9ed4d" + }, + { + "name": "Agent.Prepare on fly-a in another epoch", + "scope": { + "sessionId": "demo", + "epoch": "epoch-2", + "step": "41" + }, + "method": "Agent.Prepare", + "workerId": "fly-a", + "digest": "fa1d9cf3936aaffe3cedd0e586d7763efca2d250e89dd85b849cf35fbd91a477" + }, + { + "name": "Environment.Advance at step 41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "method": "Environment.Advance", + "workerId": "world", + "digest": "c89c390aa78ef2895d9e2b86a6000160923dc54e3be75529e0289cd18b9088b1" + } + ], + "bodies": [ + { + "name": "Agent.Prepare body", + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + }, + "digest": "74c9d5f27b2cb06922cc3ca13d87c67faea0a6c73aefbbdb21ba8f52c6d33f60" + }, + { + "name": "Environment.Advance body", + "method": "Environment.Advance", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + }, + "digest": "5aaae9c083336460a2ce34fb50e191f7108444a953f09b952e97a48b7962ee79" + }, + { + "name": "Worker.Hello body with no scope", + "method": "Worker.Hello", + "scope": null, + "params": { + "sessionId": "demo", + "expectedWorkerId": "fly-a", + "role": "agent", + "supportedMajors": [ + 1 + ] + }, + "digest": "0c07677a45178d8680dddbeec56d4e09f6ccbfe5fe77d9d4ff2fb07b369febaf" + } + ], + "pairs": [ + { + "name": "the same operation retried on a new bus call", + "left": { + "method": "Environment.Advance", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + } + }, + "right": { + "method": "Environment.Advance", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + } + }, + "workerId": "world", + "sameKey": true, + "sameBody": true, + "reason": "a retry reuses the requestId and body; only the callId changes, and the callId is not in either digest" + }, + { + "name": "the same batch id with altered controls", + "left": { + "method": "Environment.Advance", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + } + }, + "right": { + "method": "Environment.Advance", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 1.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + } + }, + "workerId": "world", + "sameKey": true, + "sameBody": false, + "reason": "same key, changed body: CONFLICT, never a second world mutation" + }, + { + "name": "params written with their keys in another order", + "left": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + } + }, + "right": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ], + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "agentId": "fly-a" + } + }, + "workerId": "fly-a", + "sameKey": true, + "sameBody": true, + "reason": "RFC 8785 sorts keys, so serialization order is not a body change" + }, + { + "name": "the same body one step later", + "left": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + } + }, + "right": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + } + }, + "workerId": "fly-a", + "sameKey": false, + "sameBody": false, + "reason": "the step is part of both the key and the body" + }, + { + "name": "the same body on another worker", + "left": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + } + }, + "right": { + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + } + }, + "workerId": "fly-a", + "rightWorkerId": "fly-b", + "sameKey": false, + "sameBody": true, + "reason": "the worker is part of the key, not of the body" + } + ], + "rejected": [ + { + "name": "a body carrying a bus callId", + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "callId": "call-2", + "agentId": "fly-a" + }, + "reason": "the canonical body excludes bus callIds" + }, + { + "name": "a body carrying a delivery id", + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "deliveryId": "dlv-7" + }, + "reason": "the canonical body excludes deliveryIds" + }, + { + "name": "a body carrying an owner token", + "method": "Agent.Commit", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "nextInput": { + "frame": { + "ownerId": "own-3" + } + } + }, + "reason": "the canonical body excludes owner tokens" + }, + { + "name": "a body carrying a pinned service incarnation", + "method": "Agent.Prepare", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "expectedIncarnation": "svc-1" + }, + "reason": "route pinning is transport state, not domain state" + }, + { + "name": "an empty method", + "method": "", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": {}, + "reason": "methods are 1..=128 printable ASCII characters" + } + ] +} diff --git a/services/flysim/crates/fly-session-types/fixtures/rational.json b/services/flysim/crates/fly-session-types/fixtures/rational.json new file mode 100644 index 0000000..f32a9ce --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/rational.json @@ -0,0 +1,249 @@ +{ + "description": "Checked rational arithmetic and the step-v1 section 5 tick accumulator.", + "accumulator": [ + { + "name": "a synthetic 60 Hz environment on a 1 ms model tick", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "tickDuration": { + "numerator": "1000000", + "denominator": "1" + }, + "steps": [ + { + "ticks": "16", + "remainder": { + "numerator": "2000000", + "denominator": "3" + } + }, + { + "ticks": "17", + "remainder": { + "numerator": "1000000", + "denominator": "3" + } + }, + { + "ticks": "17", + "remainder": { + "numerator": "0", + "denominator": "1" + } + } + ], + "totalTicks": "50", + "reason": "16, 17, 17 and a remainder of zero after three steps" + }, + { + "name": "a whole millisecond cadence never accumulates a remainder", + "stepDuration": { + "numerator": "16000000", + "denominator": "1" + }, + "tickDuration": { + "numerator": "1000000", + "denominator": "1" + }, + "steps": [ + { + "ticks": "16", + "remainder": { + "numerator": "0", + "denominator": "1" + } + }, + { + "ticks": "16", + "remainder": { + "numerator": "0", + "denominator": "1" + } + } + ], + "totalTicks": "32", + "reason": "" + }, + { + "name": "a step shorter than one tick advances nothing and keeps the remainder", + "stepDuration": { + "numerator": "500000", + "denominator": "1" + }, + "tickDuration": { + "numerator": "1000000", + "denominator": "1" + }, + "steps": [ + { + "ticks": "0", + "remainder": { + "numerator": "500000", + "denominator": "1" + } + }, + { + "ticks": "1", + "remainder": { + "numerator": "0", + "denominator": "1" + } + } + ], + "totalTicks": "1", + "reason": "the fractional period is carried, not rounded" + } + ], + "add": [ + { + "a": { + "numerator": "0", + "denominator": "1" + }, + "b": { + "numerator": "50000000", + "denominator": "3" + }, + "sum": { + "numerator": "50000000", + "denominator": "3" + } + }, + { + "a": { + "numerator": "1", + "denominator": "3" + }, + "b": { + "numerator": "1", + "denominator": "6" + }, + "sum": { + "numerator": "1", + "denominator": "2" + } + }, + { + "a": { + "numerator": "18446744073709551615", + "denominator": "1" + }, + "b": { + "numerator": "1", + "denominator": "2" + }, + "error": "reduced value does not fit U64" + } + ], + "subtract": [ + { + "a": { + "numerator": "50000000", + "denominator": "3" + }, + "b": { + "numerator": "50000000", + "denominator": "3" + }, + "difference": { + "numerator": "0", + "denominator": "1" + } + }, + { + "a": { + "numerator": "1", + "denominator": "2" + }, + "b": { + "numerator": "1", + "denominator": "3" + }, + "difference": { + "numerator": "1", + "denominator": "6" + } + }, + { + "a": { + "numerator": "0", + "denominator": "1" + }, + "b": { + "numerator": "1", + "denominator": "2" + }, + "error": "subtraction would be negative" + } + ], + "multiply": [ + { + "a": { + "numerator": "1000000", + "denominator": "1" + }, + "k": "17", + "product": { + "numerator": "17000000", + "denominator": "1" + } + }, + { + "a": { + "numerator": "0", + "denominator": "1" + }, + "k": "1000", + "product": { + "numerator": "0", + "denominator": "1" + } + }, + { + "a": { + "numerator": "18446744073709551615", + "denominator": "1" + }, + "k": "2", + "error": "reduced value does not fit U64" + } + ], + "compare": [ + { + "a": { + "numerator": "0", + "denominator": "1" + }, + "b": { + "numerator": "1000000", + "denominator": "1" + }, + "ordering": "less" + }, + { + "a": { + "numerator": "1", + "denominator": "3" + }, + "b": { + "numerator": "1", + "denominator": "3" + }, + "ordering": "equal", + "reason": "equal values compare equal; an unreduced 2/6 never reaches a comparison, because it never parses" + }, + { + "a": { + "numerator": "50000000", + "denominator": "3" + }, + "b": { + "numerator": "1000000", + "denominator": "1" + }, + "ordering": "greater" + } + ] +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/raw.json b/services/flysim/crates/fly-session-types/fixtures/raw.json new file mode 100644 index 0000000..9fed80b --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/raw.json @@ -0,0 +1,77 @@ +{ + "description": "Byte sequences every implementation must refuse before validation.", + "cases": [ + { + "name": "duplicate key at the top level", + "type": "Scope", + "base64": "eyJzZXNzaW9uSWQiOiJkZW1vIiwiZXBvY2giOiJlcG9jaC0xIiwic3RlcCI6IjEiLCJzdGVwIjoiMiJ9", + "reason": "duplicate JSON keys are refused at any depth" + }, + { + "name": "duplicate key inside a nested object", + "type": "TypedValue", + "base64": "eyJzY2hlbWEiOnsiaWQiOiJhLnYxIiwidmVyc2lvbiI6MSwiZGlnZXN0IjoiYmJhNzk1MWMwNDY2NGNkNDliYjZlZWQxZGE2ZGMxYTRlMTdlOTg5ZTc4N2JmMmU3MmY1OTMzYTVjNjE3MTM3MiJ9LCJ2YWx1ZSI6eyJ4IjoxLCJ4IjoyfX0=", + "reason": "duplicate JSON keys are refused at any depth" + }, + { + "name": "invalid UTF-8 in a string", + "type": "Scope", + "base64": "eyJzZXNzaW9uSWQiOiJkZf9tbyIsImVwb2NoIjoiZXBvY2gtMSIsInN0ZXAiOiIxIn0=", + "reason": "the envelope is UTF-8" + }, + { + "name": "invalid UTF-8 in a key", + "type": "Scope", + "base64": "eyJzZXNzaW9u/0lkIjoiZGVtbyIsImVwb2NoIjoiZXBvY2gtMSIsInN0ZXAiOiIxIn0=", + "reason": "the envelope is UTF-8" + }, + { + "name": "NaN literal", + "type": "Stimulus", + "base64": "eyJpZCI6InN0aW0tMSIsImtpbmRJZCI6InN1Z2FyIiwiZHVyYXRpb25NcyI6TmFOfQ==", + "reason": "NaN and Infinity are not JSON" + }, + { + "name": "Infinity literal", + "type": "Stimulus", + "base64": "eyJpZCI6InN0aW0tMSIsImtpbmRJZCI6InN1Z2FyIiwiZHVyYXRpb25NcyI6SW5maW5pdHl9", + "reason": "NaN and Infinity are not JSON" + }, + { + "name": "number that overflows a double", + "type": "Stimulus", + "base64": "eyJpZCI6InN0aW0tMSIsImtpbmRJZCI6InN1Z2FyIiwiZHVyYXRpb25NcyI6MWU5OTl9", + "reason": "a non-finite number never survives parsing" + }, + { + "name": "integer past the exact double range", + "type": "Stimulus", + "base64": "eyJpZCI6InN0aW0tMSIsImtpbmRJZCI6InN1Z2FyIiwiZHVyYXRpb25NcyI6OTAwNzE5OTI1NDc0MDk5M30=", + "reason": "canonical JSON cannot encode it exactly; counters are U64 strings" + }, + { + "name": "trailing data after the object", + "type": "Scope", + "base64": "eyJzZXNzaW9uSWQiOiJkZW1vIiwiZXBvY2giOiJlcG9jaC0xIiwic3RlcCI6IjEifSB7fQ==", + "reason": "one value per payload" + }, + { + "name": "truncated object", + "type": "Scope", + "base64": "eyJzZXNzaW9uSWQiOiJkZW1vIiwiZXBvY2giOiJlcG9jaC0xIg==", + "reason": "partial JSON is refused" + }, + { + "name": "empty payload", + "type": "Scope", + "base64": "", + "reason": "an empty payload is not a JSON object" + }, + { + "name": "a bare array", + "type": "Scope", + "base64": "W10=", + "reason": "a payload is an object" + } + ] +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/schema-set.json b/services/flysim/crates/fly-session-types/fixtures/schema-set.json new file mode 100644 index 0000000..2973a86 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/schema-set.json @@ -0,0 +1 @@ +{"contract":"fly-session-types","enums":[{"members":["f32le-interleaved"],"name":"AudioFormat","source":"state-media-v1 2"},{"members":["bipolar","unit"],"name":"AxisRange","source":"workers-v1 3"},{"members":["fixed-build","unverified"],"name":"Determinism","source":"workers-v1 3"},{"members":["terminal"],"name":"EpisodeRequestKind","source":"workers-v1 4"},{"members":["INVALID_ARGUMENT","UNSUPPORTED","IDENTITY_MISMATCH","STALE_EPOCH","STALE_STEP","FUTURE_STEP","INVALID_PHASE","CONFLICT","IN_PROGRESS","BUSY","BUFFER_INVALID","RESULT_EXPIRED","INCOMPATIBLE_STATE","BACKEND_FAILURE","INTERNAL"],"name":"ErrorCode","source":"ipc-v1 7"},{"members":["none","applied","unknown"],"name":"MutationCertainty","source":"ipc-v1 3"},{"members":["exact-checkpoint","episode-restart"],"name":"Recovery","source":"workers-v1 3"},{"members":["agent","environment","coordinator"],"name":"Role","source":"ipc-v1 4"},{"members":["lockstep-v1"],"name":"SchedulerId","source":"publishing-v1 3"},{"members":["rgba8"],"name":"ViewFormat","source":"state-media-v1 2"},{"members":["uninitialized","ready","preparing","prepared","advancing","committing","capturing","staged-restore","restoring","failed","stopping"],"name":"WorkerState","source":"ipc-v1 4"}],"limits":[{"name":"maxAcknowledge","source":"ipc-v1 5","value":16},{"name":"maxAgents","source":"ipc-v1 2","value":4},{"name":"maxAssets","source":"crate","value":64},{"name":"maxAttachments","source":"bus-v1 4","value":32},{"name":"maxAudioStreams","source":"crate","value":8},{"name":"maxAxes","source":"workers-v1 3","value":16},{"name":"maxButtons","source":"workers-v1 3","value":32},{"name":"maxCapabilities","source":"crate","value":32},{"name":"maxEngineFrameLength","source":"workers-v1 3","value":64},{"name":"maxEnvelopeBytes","source":"bus-v1 4","value":65536},{"name":"maxMessageCodePoints","source":"ipc-v1 7","value":512},{"name":"maxObservationDelaySteps","source":"state-media-v1 2","value":8},{"name":"maxPixelAspectPart","source":"state-media-v1 2","value":65535},{"name":"maxPorts","source":"ipc-v1 2","value":4},{"name":"maxRateRoles","source":"ipc-v1 2","value":64},{"name":"maxRewardsPerOperation","source":"workers-v1 1","value":64},{"name":"maxSampleFrames","source":"state-media-v1 2","value":192000},{"name":"maxSchemaVersion","source":"ipc-v1 2","value":65535},{"name":"maxSnapshotEvents","source":"crate","value":64},{"name":"maxStimuliPerOperation","source":"workers-v1 1","value":64},{"name":"maxSupportedMajors","source":"crate","value":8},{"name":"maxSupportedStimuli","source":"crate","value":64},{"name":"maxTypedValueBytes","source":"ipc-v1 2","value":32768},{"name":"maxViewDimension","source":"state-media-v1 2","value":4096},{"name":"maxViews","source":"workers-v1 1","value":8}],"scalars":{"ArtifactIdentity":"storeId, artifactId and generation of a bus ArtifactRef","BusCallId":"call-","Digest":"64 lowercase hexadecimal digits (SHA-256)","DomainRequestId":"req-","Id":"^[a-z0-9][a-z0-9._-]{0,63}$","OwnerToken":"dlv- or own-","U64":"\"0\" or [1-9][0-9]*, <= 18446744073709551615"},"types":[{"fields":[{"constraint":"1..=16, unique","kind":"array","name":"requestIds","required":true}],"name":"AcknowledgeParams","source":"ipc-v1 5"},{"fields":[{"constraint":"<= 16, unique, a subset of the request","kind":"array","name":"acknowledged","required":true}],"name":"AcknowledgeResult","source":"ipc-v1 5"},{"fields":[{"constraint":"","kind":"Id","name":"restoreToken","required":true}],"name":"ActivateRestoreParams","source":"state-media-v1 5"},{"fields":[{"constraint":"","kind":"U64","name":"committedStep","required":true},{"constraint":"","kind":"Id","name":"checkpointId","required":true},{"constraint":"required from an environment, null from an agent","kind":"WorldObservation|null","name":"observation","required":false}],"name":"ActivateRestoreResult","source":"state-media-v1 5"},{"fields":[{"constraint":"unique within an epoch","kind":"Id","name":"batchId","required":true},{"constraint":"one complete batch: every declared port once, descriptor order","kind":"array","name":"controls","required":true}],"name":"AdvanceParams","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"k+1","kind":"U64","name":"committedStep","required":true},{"constraint":"","kind":"Digest","name":"decisionContextDigest","required":true},{"constraint":"","kind":"AgentTelemetry","name":"telemetry","required":true}],"name":"AgentCommitResult","source":"workers-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"Id","name":"portId","required":true},{"constraint":"","kind":"Digest","name":"profileDigest","required":true},{"constraint":"","kind":"Digest","name":"datasetDigest","required":true},{"constraint":"geometry mapping needs this, not neuronCount","kind":"Digest","name":"indexDigest","required":true},{"constraint":"","kind":"U64","name":"neuronCount","required":true},{"constraint":"<= 64, unique","kind":"array","name":"rateRoles","required":true},{"constraint":"unique","kind":"array","name":"supportedStimuli","required":true}],"name":"AgentDescriptor","source":"publishing-v1 3"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"AssetRef","name":"profile","required":true},{"constraint":"signed 32-bit","kind":"int","name":"seed","required":true},{"constraint":"","kind":"SensoryInput","name":"initialInput","required":true},{"constraint":"","kind":"TypedValue","name":"initialDecisionContext","required":true},{"constraint":">= 1","kind":"int","name":"workerThreads","required":true}],"name":"AgentInitializeParams","source":"workers-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"Digest","name":"profileDigest","required":true},{"constraint":"positive","kind":"RationalNs","name":"tickDuration","required":true},{"constraint":"","kind":"U64","name":"warmupTicks","required":true},{"constraint":"\"0\"","kind":"U64","name":"committedStep","required":true},{"constraint":"","kind":"Digest","name":"decisionContextDigest","required":true},{"constraint":"","kind":"AgentTelemetry","name":"telemetry","required":true}],"name":"AgentInitializeResult","source":"workers-v1 2"},{"fields":[{"constraint":"","kind":"U64","name":"brainTicks","required":true},{"constraint":"finite and nonnegative","kind":"number","name":"populationRateHz","required":true},{"constraint":"<= 64, unique roleId, profile order, finite nonnegative hz","kind":"array<{roleId:Id,hz:number}>","name":"rates","required":true},{"constraint":"changed <= updates; signal finite","kind":"{enabled:bool,updates:U64,changed:U64,signal:number}","name":"learning","required":true}],"name":"AgentTelemetry","source":"workers-v1 1"},{"fields":[{"constraint":"","kind":"Id","name":"id","required":true},{"constraint":"","kind":"Digest","name":"digest","required":true},{"constraint":"positive","kind":"U64","name":"byteLength","required":true},{"constraint":"","kind":"Id","name":"format","required":true}],"name":"AssetRef","source":"workers-v1 1"},{"fields":[{"constraint":"","kind":"Id","name":"streamId","required":true},{"constraint":"8000..=192000","kind":"int","name":"sampleRate","required":true},{"constraint":"1..=8","kind":"int","name":"channels","required":true},{"constraint":"","kind":"AudioFormat","name":"format","required":true}],"name":"AudioDescriptor","source":"state-media-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"streamId","required":true},{"constraint":"no overlap or rewind within an epoch","kind":"U64","name":"firstSample","required":true},{"constraint":"0..=192000","kind":"int","name":"sampleFrames","required":true},{"constraint":"byteLength == sampleFrames x channels x 4, finite f32","kind":"ArtifactRef","name":"samples","required":true},{"constraint":"true on the first chunk after restore","kind":"bool","name":"discontinuity","required":true}],"name":"AudioRef","source":"state-media-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"checkpointId","required":true}],"name":"CaptureParams","source":"state-media-v1 5"},{"fields":[{"constraint":"","kind":"Id","name":"checkpointId","required":true},{"constraint":"the committed boundary","kind":"U64","name":"boundary","required":true},{"constraint":"","kind":"Digest","name":"compatibilityDigest","required":true},{"constraint":"listed attachment; digest required","kind":"ArtifactRef","name":"payload","required":true}],"name":"CaptureResult","source":"state-media-v1 5"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"DomainRequestId","name":"preparedRequestId","required":true},{"constraint":"boundary == scope.step + 1","kind":"SensoryInput","name":"nextInput","required":true},{"constraint":"","kind":"TypedValue","name":"nextDecisionContext","required":true},{"constraint":"<= 64, unique eventId, order retained","kind":"array","name":"rewards","required":true},{"constraint":"<= 64, unique id, order retained","kind":"array","name":"taskStimulations","required":true}],"name":"CommitParams","source":"workers-v1 2"},{"fields":[{"constraint":"","kind":"U64","name":"descriptorRevision","required":true},{"constraint":"","kind":"Id","name":"publisherIncarnation","required":true},{"constraint":"the committed boundary","kind":"Scope","name":"scope","required":true},{"constraint":"","kind":"Id","name":"episodeId","required":true},{"constraint":"monotonic within publisherIncarnation","kind":"U64","name":"sequence","required":true},{"constraint":"","kind":"RationalNs","name":"worldTime","required":true},{"constraint":"1..=4, unique agentId, telemetry in profile role order","kind":"array","name":"agents","required":true},{"constraint":"","kind":"TypedValue","name":"progress","required":true},{"constraint":"declared attachments held through publication admission","kind":"{views:array,audio:array}","name":"media","required":true},{"constraint":"unique, task order","kind":"array","name":"eventIds","required":true}],"name":"CommittedSnapshot","source":"publishing-v1 3"},{"fields":[{"constraint":"","kind":"SchemaRef","name":"schema","required":true},{"constraint":"<= 32, unique, fixed order","kind":"array","name":"buttons","required":true},{"constraint":"<= 16, unique id, neutral inside its range","kind":"array<{id:Id,range:AxisRange,neutral:number}>","name":"axes","required":true}],"name":"ControllerSchema","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"Digest","name":"backendDigest","required":true},{"constraint":"","kind":"Digest","name":"contentDigest","required":true},{"constraint":"","kind":"Digest","name":"configurationDigest","required":true},{"constraint":"fixed, reduced, positive","kind":"RationalNs","name":"stepDuration","required":true},{"constraint":"1..=4, unique portId, fixed order","kind":"array<{portId:Id,controls:ControllerSchema}>","name":"ports","required":true},{"constraint":"","kind":"SchemaRef","name":"inspectionSchema","required":true},{"constraint":"<= 8, unique viewId","kind":"array","name":"views","required":true},{"constraint":"unique streamId","kind":"array","name":"audio","required":true},{"constraint":"","kind":"Recovery","name":"recovery","required":true},{"constraint":"","kind":"Determinism","name":"determinism","required":true}],"name":"EnvironmentDescriptor","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"AssetRef","name":"backendConfig","required":true},{"constraint":"","kind":"AssetRef","name":"taskConfig","required":true},{"constraint":"","kind":"Id","name":"episodeId","required":true},{"constraint":"1..=4, unique portId and unique agentId","kind":"array<{portId:Id,agentId:Id}>","name":"portBindings","required":true}],"name":"EnvironmentInitializeParams","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"EnvironmentDescriptor","name":"descriptor","required":true},{"constraint":"boundary 0 and worldTime 0/1","kind":"WorldObservation","name":"observation","required":true}],"name":"EnvironmentInitializeResult","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"EpisodeRequestKind","name":"kind","required":true},{"constraint":"","kind":"Id","name":"reason","required":true},{"constraint":"","kind":"TypedValue","name":"outcome","required":true}],"name":"EpisodeRequest","source":"workers-v1 4"},{"fields":[{"constraint":"","kind":"Id","name":"sessionId","required":true},{"constraint":"","kind":"Id","name":"expectedWorkerId","required":true},{"constraint":"","kind":"Role","name":"role","required":true},{"constraint":"nonempty, unique, 1..=65535","kind":"array","name":"supportedMajors","required":true}],"name":"HelloParams","source":"ipc-v1 4"},{"fields":[{"constraint":"1","kind":"const","name":"selectedMajor","required":true},{"constraint":"0","kind":"const","name":"selectedMinor","required":true},{"constraint":"","kind":"Id","name":"workerId","required":true},{"constraint":"","kind":"Id","name":"incarnationId","required":true},{"constraint":"","kind":"Role","name":"role","required":true},{"constraint":"","kind":"Digest","name":"buildDigest","required":true},{"constraint":"","kind":"Digest","name":"contractDigest","required":true},{"constraint":"unique; agent-step-v1 or world-step-v1 required for that role","kind":"array","name":"capabilities","required":true},{"constraint":"1..=4 agents and 1..=4 ports","kind":"{maxAgents:int,maxPorts:int}","name":"limits","required":true}],"name":"HelloResult","source":"ipc-v1 4"},{"fields":[{"constraint":"","kind":"Id","name":"portId","required":true},{"constraint":"every declared button, descriptor order, no extras","kind":"array<{id:Id,down:bool}>","name":"buttons","required":true},{"constraint":"every declared axis in order; bipolar [-1,1], unit [0,1], refused not clamped","kind":"array<{id:Id,value:number}>","name":"axes","required":true}],"name":"PortControl","source":"workers-v1 3"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"Digest","name":"profileDigest","required":true},{"constraint":"positive","kind":"RationalNs","name":"interval","required":true},{"constraint":"","kind":"Digest","name":"decisionContextDigest","required":true},{"constraint":"<= 64, unique id, supplied order retained","kind":"array","name":"preStepStimulations","required":true}],"name":"PrepareParams","source":"workers-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"<= brainTicks","kind":"U64","name":"ticksAdvanced","required":true},{"constraint":"","kind":"U64","name":"brainTicks","required":true},{"constraint":">= 0 and < one model tick","kind":"RationalNs","name":"remainder","required":true},{"constraint":"the profile's registered intent schema","kind":"TypedValue","name":"decision","required":true}],"name":"PreparedDecision","source":"workers-v1 2"},{"fields":[{"constraint":"reduced against denominator","kind":"U64","name":"numerator","required":true},{"constraint":"positive; zero is encoded 0/1; arithmetic is checked","kind":"U64","name":"denominator","required":true}],"name":"RationalNs","source":"ipc-v1 2"},{"fields":[{"constraint":"unique within its outcome namespace","kind":"Id","name":"eventId","required":true},{"constraint":"","kind":"Id","name":"ruleId","required":true},{"constraint":"finite; positive-only profiles reject negatives","kind":"number","name":"value","required":true}],"name":"Reward","source":"workers-v1 1"},{"fields":[{"constraint":"","kind":"Id","name":"id","required":true},{"constraint":"1..=65535","kind":"int","name":"version","required":true},{"constraint":"64 lowercase hex digits","kind":"Digest","name":"digest","required":true}],"name":"SchemaRef","source":"ipc-v1 2"},{"fields":[{"constraint":"^[a-z0-9][a-z0-9._-]{0,63}$","kind":"Id","name":"sessionId","required":true},{"constraint":"^[a-z0-9][a-z0-9._-]{0,63}$","kind":"Id","name":"epoch","required":true},{"constraint":"decimal string, <= 18446744073709551615","kind":"U64","name":"step","required":true}],"name":"Scope","source":"ipc-v1 2"},{"fields":[{"constraint":"the observed environment boundary","kind":"U64","name":"boundary","required":true},{"constraint":"<= 8, unique viewId, producedStep <= boundary","kind":"array","name":"views","required":true},{"constraint":"a pixel-only profile rejects non-null","kind":"TypedValue|null","name":"structured","required":false}],"name":"SensoryInput","source":"workers-v1 1"},{"fields":[{"constraint":"","kind":"Id","name":"sessionId","required":true},{"constraint":"","kind":"U64","name":"revision","required":true},{"constraint":"","kind":"Digest","name":"compositionDigest","required":true},{"constraint":"","kind":"SchedulerId","name":"schedulerId","required":true},{"constraint":"","kind":"EnvironmentDescriptor","name":"environment","required":true},{"constraint":"","kind":"SchemaRef","name":"taskSchema","required":true},{"constraint":"1..=4, unique agentId and portId, each portId declared by the environment","kind":"array","name":"agents","required":true},{"constraint":"unique id","kind":"array","name":"assets","required":true}],"name":"SessionDescriptor","source":"publishing-v1 3"},{"fields":[{"constraint":"\"error\"","kind":"const","name":"type","required":true},{"constraint":"echoes the request","kind":"DomainRequestId","name":"requestId","required":true},{"constraint":"","kind":"Id","name":"workerId","required":true},{"constraint":"","kind":"Id","name":"incarnationId","required":true},{"constraint":"echoes the request scope","kind":"Scope|null","name":"scope","required":false},{"constraint":"","kind":"ErrorCode","name":"error.code","required":true},{"constraint":"<= 512 code points","kind":"string","name":"error.message","required":true},{"constraint":"none for every code raised before mutation","kind":"MutationCertainty","name":"error.mutation","required":true}],"name":"SessionRpcFailure","source":"ipc-v1 3"},{"fields":[{"constraint":"req-","kind":"DomainRequestId","name":"requestId","required":true},{"constraint":"null for lifecycle calls","kind":"Scope|null","name":"scope","required":false},{"constraint":"no bus callId/deliveryId/ownerId keys","kind":"object","name":"params","required":true}],"name":"SessionRpcRequest","source":"ipc-v1 2"},{"fields":[{"constraint":"\"result\"","kind":"const","name":"type","required":true},{"constraint":"echoes the request","kind":"DomainRequestId","name":"requestId","required":true},{"constraint":"","kind":"Id","name":"workerId","required":true},{"constraint":"","kind":"Id","name":"incarnationId","required":true},{"constraint":"echoes the request scope","kind":"Scope|null","name":"scope","required":false},{"constraint":"","kind":"object","name":"result","required":true}],"name":"SessionRpcSuccess","source":"ipc-v1 3"},{"fields":[{"constraint":"","kind":"Id","name":"reason","required":true}],"name":"ShutdownParams","source":"ipc-v1 7"},{"fields":[{"constraint":"true","kind":"const","name":"stopping","required":true}],"name":"ShutdownResult","source":"ipc-v1 7"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"AgentTelemetry","name":"telemetry","required":true},{"constraint":"null exactly at boundary 0","kind":"TypedValue|null","name":"selectedDecision","required":false},{"constraint":"null exactly at boundary 0; the agent's assigned port","kind":"PortControl|null","name":"appliedControls","required":false}],"name":"SnapshotAgent","source":"publishing-v1 3"},{"fields":[{"constraint":"","kind":"Id","name":"checkpointId","required":true},{"constraint":"provenance, not the new handles","kind":"Scope","name":"sourceScope","required":true},{"constraint":"","kind":"Digest","name":"compatibilityDigest","required":true},{"constraint":"newly imported; digest required","kind":"ArtifactRef","name":"payload","required":true}],"name":"StageRestoreParams","source":"state-media-v1 5"},{"fields":[{"constraint":"","kind":"Id","name":"checkpointId","required":true},{"constraint":"activates once, bound to scope and payload","kind":"Id","name":"restoreToken","required":true}],"name":"StageRestoreResult","source":"state-media-v1 5"},{"fields":[{"constraint":"","kind":"WorkerState","name":"state","required":true},{"constraint":"null before initialization","kind":"Scope|null","name":"currentScope","required":false},{"constraint":"","kind":"DomainRequestId|null","name":"activeRequestId","required":false},{"constraint":"","kind":"DomainRequestId|null","name":"lastCompletedRequestId","required":false},{"constraint":"","kind":"Id|null","name":"lastBatchId","required":false},{"constraint":"advances on progress, not on status queries","kind":"U64","name":"progressCounter","required":true}],"name":"StatusResult","source":"ipc-v1 4"},{"fields":[{"constraint":"echoes the request","kind":"Id","name":"batchId","required":true},{"constraint":"k","kind":"U64","name":"appliedFromStep","required":true},{"constraint":"appliedFromStep + 1","kind":"U64","name":"nextStep","required":true},{"constraint":"over validated canonical requested controls","kind":"Digest","name":"appliedControlsDigest","required":true},{"constraint":"boundary == nextStep","kind":"WorldObservation","name":"observation","required":true}],"name":"StepResult","source":"workers-v1 3"},{"fields":[{"constraint":"unique within its command namespace","kind":"Id","name":"id","required":true},{"constraint":"resolved through a profile capability","kind":"Id","name":"kindId","required":true},{"constraint":"finite and > 0","kind":"number","name":"durationMs","required":true}],"name":"Stimulus","source":"workers-v1 1"},{"fields":[{"constraint":"derived from epoch, source step, rule and ordinal","kind":"Id","name":"id","required":true},{"constraint":"","kind":"Id","name":"kindId","required":true},{"constraint":"the newly reached boundary, 0 for bootstrap","kind":"U64","name":"sourceStep","required":true},{"constraint":"","kind":"Id|null","name":"agentId","required":false},{"constraint":"","kind":"TypedValue","name":"payload","required":true}],"name":"TaskEvent","source":"workers-v1 4"},{"fields":[{"constraint":"","kind":"Id","name":"agentId","required":true},{"constraint":"","kind":"Digest","name":"profileDigest","required":true},{"constraint":"","kind":"U64","name":"ticksAdvanced","required":true},{"constraint":"","kind":"U64","name":"brainTicks","required":true},{"constraint":"","kind":"RationalNs","name":"remainder","required":true},{"constraint":"","kind":"Digest","name":"decisionDigest","required":true},{"constraint":"the commit acknowledgment","kind":"U64","name":"committedStep","required":true}],"name":"TraceAgent","source":"step-v1 8"},{"fields":[{"constraint":"","kind":"Scope","name":"scope","required":true},{"constraint":"sorted by agentId, independent of dispatch order","kind":"array","name":"agents","required":true},{"constraint":"","kind":"Id","name":"batchId","required":true},{"constraint":"","kind":"Digest","name":"controlDigest","required":true},{"constraint":"the world boundary the environment acknowledged","kind":"U64","name":"acknowledgedBoundary","required":true},{"constraint":"sorted by viewId","kind":"array<{viewId:Id,producedStep:U64}>","name":"observationBoundaries","required":true},{"constraint":"task outcome ids in task order","kind":"array","name":"outcomeIds","required":true},{"constraint":"task event ids in task order","kind":"array","name":"eventIds","required":true},{"constraint":"","kind":"U64","name":"publishedBoundary","required":true}],"name":"TraceBehaviour","source":"step-v1 8"},{"fields":[{"constraint":"","kind":"U64","name":"wallTimeNs","required":true},{"constraint":"","kind":"array<{agentId:Id,requestId:DomainRequestId}>","name":"prepareRequestIds","required":true},{"constraint":"","kind":"DomainRequestId","name":"advanceRequestId","required":true},{"constraint":"","kind":"array<{agentId:Id,requestId:DomainRequestId}>","name":"commitRequestIds","required":true},{"constraint":"call-","kind":"array","name":"busCallIds","required":true},{"constraint":"dlv- or own-","kind":"array","name":"deliveryIds","required":true}],"name":"TraceOperational","source":"step-v1 8"},{"fields":[{"constraint":"compared between runs","kind":"TraceBehaviour","name":"behaviour","required":true},{"constraint":"recorded, never compared: wall time and transport identities","kind":"TraceOperational","name":"operational","required":true}],"name":"TransitionTrace","source":"step-v1 8"},{"fields":[{"constraint":"","kind":"SchemaRef","name":"schema","required":true},{"constraint":"canonical JSON of the whole TypedValue <= 32768 bytes","kind":"object","name":"value","required":true}],"name":"TypedValue","source":"ipc-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"viewId","required":true},{"constraint":"1..=4096","kind":"int","name":"width","required":true},{"constraint":"1..=4096","kind":"int","name":"height","required":true},{"constraint":"","kind":"ViewFormat","name":"format","required":true},{"constraint":"exactly 4 x width","kind":"int","name":"rowStride","required":true},{"constraint":"positive integers <= 65535","kind":"{numerator:int,denominator:int}","name":"pixelAspect","required":true},{"constraint":"0..=8","kind":"int","name":"observationDelaySteps","required":true}],"name":"ViewDescriptor","source":"state-media-v1 2"},{"fields":[{"constraint":"","kind":"Id","name":"viewId","required":true},{"constraint":"max(0, boundary - observationDelaySteps) for required sensory views","kind":"U64","name":"producedStep","required":true},{"constraint":"listed attachment; byteLength == rowStride x height","kind":"ArtifactRef","name":"pixels","required":true}],"name":"ViewRef","source":"state-media-v1 2"},{"fields":[{"constraint":"","kind":"U64","name":"boundary","required":true},{"constraint":"logical time since episode start","kind":"RationalNs","name":"worldTime","required":true},{"constraint":"<= 64 characters","kind":"string|null","name":"engineFrame","required":false},{"constraint":"<= 8, unique viewId","kind":"array","name":"sensoryViews","required":true},{"constraint":"the descriptor's inspectionSchema","kind":"TypedValue","name":"inspection","required":true},{"constraint":"<= 8, unique viewId","kind":"array","name":"broadcastViews","required":true},{"constraint":"unique streamId","kind":"array","name":"audio","required":true}],"name":"WorldObservation","source":"workers-v1 3"}],"version":1} diff --git a/services/flysim/crates/fly-session-types/fixtures/seed-vectors.json b/services/flysim/crates/fly-session-types/fixtures/seed-vectors.json new file mode 100644 index 0000000..ecc3c5d --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/seed-vectors.json @@ -0,0 +1,185 @@ +{ + "description": "seed-derivation-v1 test vectors. Both languages must reproduce every seed.", + "algorithm": "seed-derivation-v1", + "prefix": "flybrain/seed-derivation-v1", + "materialTemplate": "\\n\\n\\n", + "rule": "SHA-256 of the material, read as eight big-endian u32 lanes; the first nonzero lane is the seed as a two's-complement i32.", + "vectors": [ + { + "masterSeed": "0", + "agentId": "fly-a", + "material": "flybrain/seed-derivation-v1\n0\nfly-a\n", + "materialDigest": "6cf7c34a422f4cdd890c64d0ef5a072e4f6ddc2ee18e83ee24a5f2214f4960d1", + "seed": 1828176714 + }, + { + "masterSeed": "0", + "agentId": "fly-b", + "material": "flybrain/seed-derivation-v1\n0\nfly-b\n", + "materialDigest": "48a52f4069cfca001ba5ee6ae870dccb5a5bd59d61babf333a01749b6a8f4025", + "seed": 1218785088 + }, + { + "masterSeed": "0", + "agentId": "fly-c", + "material": "flybrain/seed-derivation-v1\n0\nfly-c\n", + "materialDigest": "53a83c2d0cfce5a3356aab3b0b73a270201a2dff7816168a4167c71a32d863fe", + "seed": 1403534381 + }, + { + "masterSeed": "0", + "agentId": "fly-d", + "material": "flybrain/seed-derivation-v1\n0\nfly-d\n", + "materialDigest": "f202513f32fe6070eee65ad72027490f9218b86dc8cf74e4e3563f02d532ea94", + "seed": -234729153 + }, + { + "masterSeed": "1", + "agentId": "fly-a", + "material": "flybrain/seed-derivation-v1\n1\nfly-a\n", + "materialDigest": "6eab9d6d6d002ffc0e2cdf2d64dd8226f90c391e45641512d14edbb244225698", + "seed": 1856740717 + }, + { + "masterSeed": "1", + "agentId": "fly-b", + "material": "flybrain/seed-derivation-v1\n1\nfly-b\n", + "materialDigest": "00115cb341d839490c5485462fd7eedd8b6baeda3748054fcba0d1558e471a6c", + "seed": 1137843 + }, + { + "masterSeed": "1", + "agentId": "fly-c", + "material": "flybrain/seed-derivation-v1\n1\nfly-c\n", + "materialDigest": "af97482074d6d36e5f5920aaa1d87f02365045f3712300dbd243584f2cb4a64c", + "seed": -1349040096 + }, + { + "masterSeed": "1", + "agentId": "fly-d", + "material": "flybrain/seed-derivation-v1\n1\nfly-d\n", + "materialDigest": "1081b24880040dcc4d442f88451513e4fbcf8dae9ccb79329dd289fabbc7938b", + "seed": 276935240 + }, + { + "masterSeed": "42", + "agentId": "fly-a", + "material": "flybrain/seed-derivation-v1\n42\nfly-a\n", + "materialDigest": "f4f9f271d53e94c38c6260b99840513f9eb70e5bb442d63345e217185e6ba9f6", + "seed": -184946063 + }, + { + "masterSeed": "42", + "agentId": "fly-b", + "material": "flybrain/seed-derivation-v1\n42\nfly-b\n", + "materialDigest": "1feb285e834138ec823ffea696fbe5fb3c211f349ce17140e25286d06c56ece8", + "seed": 535504990 + }, + { + "masterSeed": "42", + "agentId": "fly-c", + "material": "flybrain/seed-derivation-v1\n42\nfly-c\n", + "materialDigest": "b5a251ed4521f91eab1d8a6813880794c5c6706eafc137d8ebc169f743c60eed", + "seed": -1247653395 + }, + { + "masterSeed": "42", + "agentId": "fly-d", + "material": "flybrain/seed-derivation-v1\n42\nfly-d\n", + "materialDigest": "e36d6e40f27a4ffe473351d4bd01154da2efb3c3038d88f3aa48f77963d465c6", + "seed": -479367616 + }, + { + "masterSeed": "9223372036854775808", + "agentId": "fly-a", + "material": "flybrain/seed-derivation-v1\n9223372036854775808\nfly-a\n", + "materialDigest": "d59f513b6fdce1646ed13907302e0cb3ca4696738dd7df38e9581397508ec933", + "seed": -710979269 + }, + { + "masterSeed": "9223372036854775808", + "agentId": "fly-b", + "material": "flybrain/seed-derivation-v1\n9223372036854775808\nfly-b\n", + "materialDigest": "c8de8ca0279829c438fa19c07f05768cfe050e8c21bdaa0fe3c68f8048b4e1a3", + "seed": -924939104 + }, + { + "masterSeed": "9223372036854775808", + "agentId": "fly-c", + "material": "flybrain/seed-derivation-v1\n9223372036854775808\nfly-c\n", + "materialDigest": "354ad1ec388aa7be6136151d0f6281cc17279b62e95d458c96f392aaac40ee62", + "seed": 894095852 + }, + { + "masterSeed": "9223372036854775808", + "agentId": "fly-d", + "material": "flybrain/seed-derivation-v1\n9223372036854775808\nfly-d\n", + "materialDigest": "58e61deb4777b7702bdcd7edf5a5bcd0d51273699cc1051c7fbc9a5ffedd9e15", + "seed": 1491475947 + }, + { + "masterSeed": "18446744073709551615", + "agentId": "fly-a", + "material": "flybrain/seed-derivation-v1\n18446744073709551615\nfly-a\n", + "materialDigest": "f888eb95bcab276936fce5f72df3a0741e36da26722f43cc2a974932dfd42cd1", + "seed": -125244523 + }, + { + "masterSeed": "18446744073709551615", + "agentId": "fly-b", + "material": "flybrain/seed-derivation-v1\n18446744073709551615\nfly-b\n", + "materialDigest": "d343df049e8ef71617e9af7a321cf498d61083c50229f2d6852077670c86acbd", + "seed": -750526716 + }, + { + "masterSeed": "18446744073709551615", + "agentId": "fly-c", + "material": "flybrain/seed-derivation-v1\n18446744073709551615\nfly-c\n", + "materialDigest": "88016b220245b431a4eb510d31b643475bb2496130b65ae81b165acd5a899292", + "seed": -2013172958 + }, + { + "masterSeed": "18446744073709551615", + "agentId": "fly-d", + "material": "flybrain/seed-derivation-v1\n18446744073709551615\nfly-d\n", + "materialDigest": "d9b5bee718c87599c9ef0b3c16f361dadf714a33f7dd1209a21108f4df700b16", + "seed": -642400537 + } + ], + "composition": { + "masterSeed": "42", + "agentIds": [ + "fly-a", + "fly-b", + "fly-c", + "fly-d" + ], + "seeds": [ + -184946063, + 535504990, + -1247653395, + -479367616 + ], + "reason": "independent per-agent seeds from one recorded master seed and stable agent ids" + }, + "invalid": [ + { + "masterSeed": "0", + "agentId": "Fly-A", + "reason": "an agent id is an Id: lowercase" + }, + { + "masterSeed": "0", + "agentId": "", + "reason": "an agent id is 1..=64 characters" + }, + { + "masterSeed": "0", + "agentIds": [ + "fly-a", + "fly-a" + ], + "reason": "a composition with a repeated agent id is refused rather than silently sharing a seed" + } + ] +} diff --git a/services/flysim/crates/fly-session-types/fixtures/traces.json b/services/flysim/crates/fly-session-types/fixtures/traces.json new file mode 100644 index 0000000..71d8189 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/traces.json @@ -0,0 +1,1141 @@ +{ + "description": "step-v1 section 8: sequential, concurrent and reversed runs must agree on behaviour, and only on behaviour.", + "baseline": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "variants": [ + { + "name": "reversed dispatch and completion order", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + }, + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "spectator", + "producedStep": "41" + }, + { + "viewId": "screen", + "producedStep": "42" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-b", + "requestId": "req-42" + }, + { + "agentId": "fly-a", + "requestId": "req-41" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-b", + "requestId": "req-45" + }, + { + "agentId": "fly-a", + "requestId": "req-44" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": true, + "reason": "completion order never affects the committed result" + }, + { + "name": "a safe retry with fresh bus callIds, delivery ids and wall time", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "9999999999", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-500", + "call-501", + "call-502", + "call-503" + ], + "deliveryIds": [ + "dlv-91", + "own-92" + ] + } + }, + "behaviourEquals": true, + "reason": "a retry has a new transport correlation and the same behaviour" + }, + { + "name": "another run's domain request ids", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-1041" + }, + { + "agentId": "fly-b", + "requestId": "req-1042" + } + ], + "advanceRequestId": "req-1043", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-1044" + }, + { + "agentId": "fly-b", + "requestId": "req-1045" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": true, + "reason": "step-v1 section 8 excludes request ids from the comparison" + }, + { + "name": "one extra neural tick", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "18", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "agent fly-a" + }, + { + "name": "a different tick remainder", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "agent fly-a" + }, + { + "name": "a different decision digest", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "3c7a9d8634cce0a0624d30476695f94c9c1f31d47d471396497db4f562a2e068", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "agent fly-a" + }, + { + "name": "a different control digest", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "b80f397807afe6543058911e501c7f2945266a04badaad4815997e17c908d38f", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "controlDigest" + }, + { + "name": "a different batch id", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-999", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "batchId" + }, + { + "name": "an observation produced at another boundary", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "41" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "observationBoundaries" + }, + { + "name": "task events in another order", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-2", + "evt-1" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "eventIds" + }, + { + "name": "a different agent in the transition", + "trace": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-c", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + }, + { + "viewId": "spectator", + "producedStep": "41" + } + ], + "outcomeIds": [ + "outcome-1", + "outcome-2" + ], + "eventIds": [ + "evt-1", + "evt-2" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-c", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-c", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "behaviourEquals": false, + "diffContains": "agents" + } + ] +} \ No newline at end of file diff --git a/services/flysim/crates/fly-session-types/fixtures/valid.json b/services/flysim/crates/fly-session-types/fixtures/valid.json new file mode 100644 index 0000000..c316f95 --- /dev/null +++ b/services/flysim/crates/fly-session-types/fixtures/valid.json @@ -0,0 +1,2787 @@ +{ + "description": "Payloads every implementation must accept, round trip and canonicalize identically.", + "cases": [ + { + "name": "scope at step 41", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "note": "", + "canonical": "{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}", + "digest": "1953a38efa113f682ee69e2db93e9cf01e17638f3a82af6d4b1666dde8c25d7b" + }, + { + "name": "scope at boundary zero", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "0" + }, + "note": "", + "canonical": "{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"0\"}", + "digest": "de1105b866a42a79ba7162e9b2c914dd1da8d96b11b65f0458baddd17d6e356c" + }, + { + "name": "scope at the U64 maximum", + "type": "Scope", + "value": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "18446744073709551615" + }, + "note": "", + "canonical": "{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"18446744073709551615\"}", + "digest": "d941cef9e565492376d10812c27787d661bbee13995e9310ddaabb7aa55cc2bd" + }, + { + "name": "rational zero in its only legal form", + "type": "RationalNs", + "value": { + "numerator": "0", + "denominator": "1" + }, + "note": "zero is encoded 0/1", + "canonical": "{\"denominator\":\"1\",\"numerator\":\"0\"}", + "digest": "a93ea196102f24ae67e9e177f75ac40c9597c06d361c37fbc5a742f517c55425" + }, + { + "name": "reduced 60 Hz step duration", + "type": "RationalNs", + "value": { + "numerator": "50000000", + "denominator": "3" + }, + "note": "", + "canonical": "{\"denominator\":\"3\",\"numerator\":\"50000000\"}", + "digest": "952c3304ef49b0e8422de5a46b0c95c3e2a54db8608a605dbfe195b84dc686d2" + }, + { + "name": "one millisecond tick duration", + "type": "RationalNs", + "value": { + "numerator": "1000000", + "denominator": "1" + }, + "note": "", + "canonical": "{\"denominator\":\"1\",\"numerator\":\"1000000\"}", + "digest": "663997e4702a3fb1a303fcd3500e11fcda0d662e20c995b1fa070e32c65e7ad3" + }, + { + "name": "schema reference at version 1", + "type": "SchemaRef", + "value": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "note": "", + "canonical": "{\"digest\":\"80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1\",\"id\":\"pokemon.progress.v1\",\"version\":1}", + "digest": "929dcd71459199d62b8e215c0866e8ade178ff4ed5f24f8aa11b9e33c8380122" + }, + { + "name": "schema reference at the maximum version", + "type": "SchemaRef", + "value": { + "id": "pokemon.progress.v1", + "version": 65535, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "note": "", + "canonical": "{\"digest\":\"80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1\",\"id\":\"pokemon.progress.v1\",\"version\":65535}", + "digest": "7d2ce2fe9d99be170ca980d0c47ffa49bff5800bd21a371a92588e015b4ee55d" + }, + { + "name": "typed value", + "type": "TypedValue", + "value": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10, + "badges": 2 + } + }, + "note": "", + "canonical": "{\"schema\":{\"digest\":\"80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1\",\"id\":\"pokemon.progress.v1\",\"version\":1},\"value\":{\"badges\":2,\"rank\":10}}", + "digest": "d0e2d096f61633567f6959e3bbfb6e8f1f2bb507a3c54219118b9eee96c20e32" + }, + { + "name": "typed value with an empty object", + "type": "TypedValue", + "value": { + "schema": { + "id": "empty.v1", + "version": 1, + "digest": "b86e38af20e53d5426f2ae6cd496f258bf7592bd047aea2b0f6e41dfed7ef79e" + }, + "value": {} + }, + "note": "", + "canonical": "{\"schema\":{\"digest\":\"b86e38af20e53d5426f2ae6cd496f258bf7592bd047aea2b0f6e41dfed7ef79e\",\"id\":\"empty.v1\",\"version\":1},\"value\":{}}", + "digest": "a39dd4af1f8581cdc2afaa6d86fb7b5b4c384dff5ea6d98212dc7b983d0a5d0b" + }, + { + "name": "asset reference", + "type": "AssetRef", + "value": { + "id": "profile-fly-a", + "digest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "byteLength": "4096", + "format": "flyprofile" + }, + "note": "", + "canonical": "{\"byteLength\":\"4096\",\"digest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"format\":\"flyprofile\",\"id\":\"profile-fly-a\"}", + "digest": "9fef94f7c3f2601a13d235077441c5d28f604b68a3156d1ad01c5ad26e953c49" + }, + { + "name": "sensory input with one view", + "type": "SensoryInput", + "value": { + "boundary": "1", + "views": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "note": "", + "canonical": "{\"boundary\":\"1\",\"structured\":null,\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"1\",\"viewId\":\"screen\"}]}", + "digest": "41754900becead6be965919ca73661069ef2e25ac4ba2c44717e1419de91970a" + }, + { + "name": "sensory input with no views at bootstrap", + "type": "SensoryInput", + "value": { + "boundary": "0", + "views": [], + "structured": null + }, + "note": "", + "canonical": "{\"boundary\":\"0\",\"structured\":null,\"views\":[]}", + "digest": "7f92dff3688a5042f2aeec4ca6e902f53db84635651f780b03a26c2e3356d42d" + }, + { + "name": "sensory input with structured sensing", + "type": "SensoryInput", + "value": { + "boundary": "3", + "views": [ + { + "viewId": "screen", + "producedStep": "3", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": { + "schema": { + "id": "arena.features.v1", + "version": 1, + "digest": "81414e2c9750102a91d75ba71b8d3dd5161f3e2d69a0c470c6961033def0710d" + }, + "value": { + "near": 1 + } + } + }, + "note": "", + "canonical": "{\"boundary\":\"3\",\"structured\":{\"schema\":{\"digest\":\"81414e2c9750102a91d75ba71b8d3dd5161f3e2d69a0c470c6961033def0710d\",\"id\":\"arena.features.v1\",\"version\":1},\"value\":{\"near\":1}},\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"3\",\"viewId\":\"screen\"}]}", + "digest": "e491f273bd045b3f954bc1b5051b551868346fc28115a2e13781b696c1256757" + }, + { + "name": "stimulus", + "type": "Stimulus", + "value": { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + }, + "note": "", + "canonical": "{\"durationMs\":50,\"id\":\"stim-1\",\"kindId\":\"sugar\"}", + "digest": "67590e9e9852976a074d7ae91632c4f10cde7264459f300a0cc1903077ed58c5" + }, + { + "name": "reward", + "type": "Reward", + "value": { + "eventId": "evt-1", + "ruleId": "badge", + "value": 1.5 + }, + "note": "", + "canonical": "{\"eventId\":\"evt-1\",\"ruleId\":\"badge\",\"value\":1.5}", + "digest": "95f4c5f144f496f85642fffa483cd94dc674e332a0d7e95e88e664796d438801" + }, + { + "name": "reward of zero", + "type": "Reward", + "value": { + "eventId": "evt-2", + "ruleId": "idle", + "value": 0.0 + }, + "note": "", + "canonical": "{\"eventId\":\"evt-2\",\"ruleId\":\"idle\",\"value\":0}", + "digest": "7bb6160ed3e10345e4084118546ff27725eea5666407b483e23bd9b95a87a51e" + }, + { + "name": "agent telemetry", + "type": "AgentTelemetry", + "value": { + "brainTicks": "17", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "note": "", + "canonical": "{\"brainTicks\":\"17\",\"learning\":{\"changed\":\"2\",\"enabled\":true,\"signal\":0.5,\"updates\":\"4\"},\"populationRateHz\":12.5,\"rates\":[{\"hz\":3.25,\"roleId\":\"kenyon\"},{\"hz\":0,\"roleId\":\"mbon\"}]}", + "digest": "de4c4e0a2cc3cac459929480a057495029a74e67e9444ea4e197b403659155ed" + }, + { + "name": "agent telemetry with no tracked roles", + "type": "AgentTelemetry", + "value": { + "brainTicks": "0", + "populationRateHz": 0.0, + "rates": [], + "learning": { + "enabled": false, + "updates": "0", + "changed": "0", + "signal": 0.0 + } + }, + "note": "", + "canonical": "{\"brainTicks\":\"0\",\"learning\":{\"changed\":\"0\",\"enabled\":false,\"signal\":0,\"updates\":\"0\"},\"populationRateHz\":0,\"rates\":[]}", + "digest": "51c684931ea3d7c54f67a78a90d676944d9527b0bc4465d5a6d4e02b87ff7485" + }, + { + "name": "session rpc request with a scope", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-41", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "params": { + "agentId": "fly-a" + } + }, + "note": "", + "canonical": "{\"params\":{\"agentId\":\"fly-a\"},\"requestId\":\"req-41\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}}", + "digest": "236215ca3453d8d07cbaa4cfe3b5c9d7c92ed145bd8e6cd3b85a9adcd1de77af" + }, + { + "name": "session rpc request without a scope", + "type": "SessionRpcRequest", + "value": { + "requestId": "req-1", + "scope": null, + "params": {} + }, + "note": "", + "canonical": "{\"params\":{},\"requestId\":\"req-1\",\"scope\":null}", + "digest": "2442374424ed68182d5c77ee66f026caeb286bccd836d9713e8def9e50b55976" + }, + { + "name": "session rpc success", + "type": "SessionRpcSuccess", + "value": { + "type": "result", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "result": { + "agentId": "fly-a" + } + }, + "note": "", + "canonical": "{\"incarnationId\":\"inc-1\",\"requestId\":\"req-41\",\"result\":{\"agentId\":\"fly-a\"},\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"},\"type\":\"result\",\"workerId\":\"fly-a\"}", + "digest": "d4bf3e4a30901d8936a3b1034447cd2ca4d6821de832bfb63018d138f504d5d9" + }, + { + "name": "session rpc failure before mutation", + "type": "SessionRpcFailure", + "value": { + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "error": { + "code": "INVALID_ARGUMENT", + "message": "interval is not reduced", + "mutation": "none" + } + }, + "note": "", + "canonical": "{\"error\":{\"code\":\"INVALID_ARGUMENT\",\"message\":\"interval is not reduced\",\"mutation\":\"none\"},\"incarnationId\":\"inc-1\",\"requestId\":\"req-41\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"},\"type\":\"error\",\"workerId\":\"fly-a\"}", + "digest": "c7cd2c928eb476a53edb1ed6ff297b4c1097fd26d3934da1c8823bb63f8670e2" + }, + { + "name": "session rpc failure with uncertain mutation", + "type": "SessionRpcFailure", + "value": { + "type": "error", + "requestId": "req-42", + "workerId": "world", + "incarnationId": "inc-2", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "error": { + "code": "BACKEND_FAILURE", + "message": "backend stopped responding", + "mutation": "unknown" + } + }, + "note": "", + "canonical": "{\"error\":{\"code\":\"BACKEND_FAILURE\",\"message\":\"backend stopped responding\",\"mutation\":\"unknown\"},\"incarnationId\":\"inc-2\",\"requestId\":\"req-42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"42\"},\"type\":\"error\",\"workerId\":\"world\"}", + "digest": "9f59e69015ec7b9dea324242cd1e63d9d79d8229b389ea67f6b2e51f927eeadf" + }, + { + "name": "agent initialize params", + "type": "AgentInitializeParams", + "value": { + "agentId": "fly-a", + "profile": { + "id": "profile-fly-a", + "digest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "byteLength": "4096", + "format": "flyprofile" + }, + "seed": -2147483648, + "initialInput": { + "boundary": "0", + "views": [ + { + "viewId": "screen", + "producedStep": "0", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "initialDecisionContext": { + "schema": { + "id": "gameboy.context.v1", + "version": 1, + "digest": "e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0" + }, + "value": { + "available": [ + "a", + "start" + ] + } + }, + "workerThreads": 3 + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"initialDecisionContext\":{\"schema\":{\"digest\":\"e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0\",\"id\":\"gameboy.context.v1\",\"version\":1},\"value\":{\"available\":[\"a\",\"start\"]}},\"initialInput\":{\"boundary\":\"0\",\"structured\":null,\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"0\",\"viewId\":\"screen\"}]},\"profile\":{\"byteLength\":\"4096\",\"digest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"format\":\"flyprofile\",\"id\":\"profile-fly-a\"},\"seed\":-2147483648,\"workerThreads\":3}", + "digest": "d5d768bc0d130d87dc0020cf95648b77a68c15d63e1acac99c89df0c7de3eb98" + }, + { + "name": "agent initialize result", + "type": "AgentInitializeResult", + "value": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "tickDuration": { + "numerator": "1000000", + "denominator": "1" + }, + "warmupTicks": "2500", + "committedStep": "0", + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "telemetry": { + "brainTicks": "2500", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + } + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"committedStep\":\"0\",\"decisionContextDigest\":\"ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"telemetry\":{\"brainTicks\":\"2500\",\"learning\":{\"changed\":\"2\",\"enabled\":true,\"signal\":0.5,\"updates\":\"4\"},\"populationRateHz\":12.5,\"rates\":[{\"hz\":3.25,\"roleId\":\"kenyon\"},{\"hz\":0,\"roleId\":\"mbon\"}]},\"tickDuration\":{\"denominator\":\"1\",\"numerator\":\"1000000\"},\"warmupTicks\":\"2500\"}", + "digest": "a220160c75d758720fe43145089939201ee35c86bb100fae0c4efdd3c4eafaa6" + }, + { + "name": "prepare params", + "type": "PrepareParams", + "value": { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [ + { + "id": "stim-1", + "kindId": "sugar", + "durationMs": 50.0 + } + ] + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"decisionContextDigest\":\"ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4\",\"interval\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"preStepStimulations\":[{\"durationMs\":50,\"id\":\"stim-1\",\"kindId\":\"sugar\"}],\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\"}", + "digest": "cdad8a625a5d33b5029a587b91959c234b265d2e23aa2f8e86169abc2cca8730" + }, + { + "name": "prepare params with no stimulation", + "type": "PrepareParams", + "value": { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "interval": { + "numerator": "50000000", + "denominator": "3" + }, + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "preStepStimulations": [] + }, + "note": "", + "canonical": "{\"agentId\":\"fly-b\",\"decisionContextDigest\":\"ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4\",\"interval\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"preStepStimulations\":[],\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\"}", + "digest": "549b9f9d93ac1d826f3bba2a57f1293b1371404304ae8ab2c173f05f76b78065" + }, + { + "name": "prepared decision", + "type": "PreparedDecision", + "value": { + "agentId": "fly-a", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + } + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"decision\":{\"schema\":{\"digest\":\"e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d\",\"id\":\"gameboy.intent.v1\",\"version\":1},\"value\":{\"press\":\"a\"}},\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"}", + "digest": "55f6ba23215c294f85515712613f7c1eb16a7e4e395556da273b65e0ad871698" + }, + { + "name": "prepared decision with a zero remainder", + "type": "PreparedDecision", + "value": { + "agentId": "fly-a", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "none" + } + } + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"brainTicks\":\"2550\",\"decision\":{\"schema\":{\"digest\":\"e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d\",\"id\":\"gameboy.intent.v1\",\"version\":1},\"value\":{\"press\":\"none\"}},\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}", + "digest": "36f799eb6897274d8bc7afe96d5dd49fd68a15d47b4d6ea15bcfc6057018228a" + }, + { + "name": "commit params", + "type": "CommitParams", + "value": { + "agentId": "fly-a", + "preparedRequestId": "req-41", + "nextInput": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "nextDecisionContext": { + "schema": { + "id": "gameboy.context.v1", + "version": 1, + "digest": "e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0" + }, + "value": { + "available": [ + "b" + ] + } + }, + "rewards": [ + { + "eventId": "evt-1", + "ruleId": "badge", + "value": 1.5 + } + ], + "taskStimulations": [ + { + "id": "stim-2", + "kindId": "sugar", + "durationMs": 25.0 + } + ] + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"nextDecisionContext\":{\"schema\":{\"digest\":\"e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0\",\"id\":\"gameboy.context.v1\",\"version\":1},\"value\":{\"available\":[\"b\"]}},\"nextInput\":{\"boundary\":\"42\",\"structured\":null,\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}]},\"preparedRequestId\":\"req-41\",\"rewards\":[{\"eventId\":\"evt-1\",\"ruleId\":\"badge\",\"value\":1.5}],\"taskStimulations\":[{\"durationMs\":25,\"id\":\"stim-2\",\"kindId\":\"sugar\"}]}", + "digest": "cb55fa3e34b0a895f69b59359035120fcd9205dfc8bb4b0b3cd5097b58852dd7" + }, + { + "name": "commit params with empty outcome arrays", + "type": "CommitParams", + "value": { + "agentId": "fly-b", + "preparedRequestId": "req-41", + "nextInput": { + "boundary": "42", + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "structured": null + }, + "nextDecisionContext": { + "schema": { + "id": "gameboy.context.v1", + "version": 1, + "digest": "e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0" + }, + "value": { + "available": [] + } + }, + "rewards": [], + "taskStimulations": [] + }, + "note": "", + "canonical": "{\"agentId\":\"fly-b\",\"nextDecisionContext\":{\"schema\":{\"digest\":\"e37846f922a5c35b218684fb650d9f2566db7d93dc8eb0e2deb86e22860784b0\",\"id\":\"gameboy.context.v1\",\"version\":1},\"value\":{\"available\":[]}},\"nextInput\":{\"boundary\":\"42\",\"structured\":null,\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}]},\"preparedRequestId\":\"req-41\",\"rewards\":[],\"taskStimulations\":[]}", + "digest": "698d48a40e9438e7973af49afa13e92f1e3d4681cea71bc75a38a8f41310f86b" + }, + { + "name": "agent commit result", + "type": "AgentCommitResult", + "value": { + "agentId": "fly-a", + "committedStep": "42", + "decisionContextDigest": "ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + } + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"committedStep\":\"42\",\"decisionContextDigest\":\"ea7792a26f405e2ae9c6f49ca93bbe6076ceac0a1fc53d83426c7d7f2d9377e4\",\"telemetry\":{\"brainTicks\":\"2534\",\"learning\":{\"changed\":\"2\",\"enabled\":true,\"signal\":0.5,\"updates\":\"4\"},\"populationRateHz\":12.5,\"rates\":[{\"hz\":3.25,\"roleId\":\"kenyon\"},{\"hz\":0,\"roleId\":\"mbon\"}]}}", + "digest": "9f0a8812101f8f41a147846f6a94b67ae7898ca143fa3cf44ae3a3d5924cd17c" + }, + { + "name": "controller schema", + "type": "ControllerSchema", + "value": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + }, + "note": "", + "canonical": "{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}}", + "digest": "4a551ed563aa1fae4a4598a5547eb58367ed9a81c3db706b89e7aca1fb50ff6d" + }, + { + "name": "port control at neutral", + "type": "PortControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + "note": "", + "canonical": "{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-1\"}", + "digest": "305732269e99981d7cac457acd0f711ff3687ba9acab2af2fddb9587401456b5" + }, + { + "name": "port control at the analog limits", + "type": "PortControl", + "value": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": -1.0 + }, + { + "id": "trigger", + "value": 1.0 + } + ] + }, + "note": "", + "canonical": "{\"axes\":[{\"id\":\"stick-x\",\"value\":-1},{\"id\":\"trigger\",\"value\":1}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-1\"}", + "digest": "11cef7568389c537fd826845523021f3408762a5a8b8d7827e6a62e5a9116e02" + }, + { + "name": "environment descriptor with one port", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "note": "", + "canonical": "{\"audio\":[{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}],\"backendDigest\":\"10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be\",\"configurationDigest\":\"b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1\",\"contentDigest\":\"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\",\"determinism\":\"fixed-build\",\"inspectionSchema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"ports\":[{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-1\"}],\"recovery\":\"exact-checkpoint\",\"stepDuration\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"views\":[{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}]}", + "digest": "885834e2ba9a5dee35b12a7844768746aa2842fec2f07622ead180905fa11d8f" + }, + { + "name": "environment descriptor with four ports", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-2", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-3", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + }, + { + "portId": "port-4", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "note": "", + "canonical": "{\"audio\":[{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}],\"backendDigest\":\"10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be\",\"configurationDigest\":\"b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1\",\"contentDigest\":\"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\",\"determinism\":\"fixed-build\",\"inspectionSchema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"ports\":[{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-1\"},{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-2\"},{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-3\"},{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-4\"}],\"recovery\":\"exact-checkpoint\",\"stepDuration\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"views\":[{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}]}", + "digest": "a8e29d9cdb27e83478f86450e8a24b287de2def947860ba0b85147ef9baf23ec" + }, + { + "name": "environment descriptor that only restarts episodes", + "type": "EnvironmentDescriptor", + "value": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "episode-restart", + "determinism": "fixed-build" + }, + "note": "", + "canonical": "{\"audio\":[{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}],\"backendDigest\":\"10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be\",\"configurationDigest\":\"b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1\",\"contentDigest\":\"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\",\"determinism\":\"fixed-build\",\"inspectionSchema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"ports\":[{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-1\"}],\"recovery\":\"episode-restart\",\"stepDuration\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"views\":[{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}]}", + "digest": "e46887f6c10989a8b1bcd8e5bfa40e66b33b3208389ee6ebad7907ca9277e6e3" + }, + { + "name": "environment initialize params", + "type": "EnvironmentInitializeParams", + "value": { + "backendConfig": { + "id": "backend-gb", + "digest": "b86130f294ef46c5343a505946e6e0507f061d7a1bcf46f09c6d42e1aeb9a2a1", + "byteLength": "512", + "format": "flybackend" + }, + "taskConfig": { + "id": "task-escape", + "digest": "b48eb1e3b6f871c6384b86387e59d4e3b6c69245168011968d5982204c656ac6", + "byteLength": "256", + "format": "flytask" + }, + "episodeId": "episode-1", + "portBindings": [ + { + "portId": "port-1", + "agentId": "fly-a" + } + ] + }, + "note": "", + "canonical": "{\"backendConfig\":{\"byteLength\":\"512\",\"digest\":\"b86130f294ef46c5343a505946e6e0507f061d7a1bcf46f09c6d42e1aeb9a2a1\",\"format\":\"flybackend\",\"id\":\"backend-gb\"},\"episodeId\":\"episode-1\",\"portBindings\":[{\"agentId\":\"fly-a\",\"portId\":\"port-1\"}],\"taskConfig\":{\"byteLength\":\"256\",\"digest\":\"b48eb1e3b6f871c6384b86387e59d4e3b6c69245168011968d5982204c656ac6\",\"format\":\"flytask\",\"id\":\"task-escape\"}}", + "digest": "be5e458e0e900e16b3a4bb3befe032047463c5d2576f424e3c0f32d1ed194012" + }, + { + "name": "environment initialize result at boundary zero", + "type": "EnvironmentInitializeResult", + "value": { + "descriptor": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "observation": { + "boundary": "0", + "worldTime": { + "numerator": "0", + "denominator": "1" + }, + "engineFrame": null, + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "0", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 0, + "x": 0, + "y": 0 + } + }, + "broadcastViews": [], + "audio": [] + } + }, + "note": "", + "canonical": "{\"descriptor\":{\"audio\":[{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}],\"backendDigest\":\"10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be\",\"configurationDigest\":\"b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1\",\"contentDigest\":\"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\",\"determinism\":\"fixed-build\",\"inspectionSchema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"ports\":[{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-1\"}],\"recovery\":\"exact-checkpoint\",\"stepDuration\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"views\":[{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}]},\"observation\":{\"audio\":[],\"boundary\":\"0\",\"broadcastViews\":[],\"engineFrame\":null,\"inspection\":{\"schema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"value\":{\"map\":0,\"x\":0,\"y\":0}},\"sensoryViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"0\",\"viewId\":\"screen\"}],\"worldTime\":{\"denominator\":\"1\",\"numerator\":\"0\"}}}", + "digest": "f7a51022984e5584f8cb366e09673a814755a4154520ba802867947f44824769" + }, + { + "name": "world observation", + "type": "WorldObservation", + "value": { + "boundary": "1", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "1", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "800", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + }, + "note": "", + "canonical": "{\"audio\":[{\"discontinuity\":false,\"firstSample\":\"800\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}],\"boundary\":\"1\",\"broadcastViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"1\",\"viewId\":\"screen\"}],\"engineFrame\":\"1\",\"inspection\":{\"schema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"value\":{\"map\":40,\"x\":5,\"y\":7}},\"sensoryViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"1\",\"viewId\":\"screen\"}],\"worldTime\":{\"denominator\":\"3\",\"numerator\":\"50000000\"}}", + "digest": "f60d51dab4cf43c98b55724ee33fae362092527c3e76ebc480eca3a3be4b47cd" + }, + { + "name": "advance params", + "type": "AdvanceParams", + "value": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + }, + "note": "", + "canonical": "{\"batchId\":\"batch-41\",\"controls\":[{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-1\"}]}", + "digest": "1b6088e49d17d1d58d415348018f36be0df2b758f27267839d1d31cc434abe32" + }, + { + "name": "advance params for four ports", + "type": "AdvanceParams", + "value": { + "batchId": "batch-41", + "controls": [ + { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-2", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-3", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + }, + { + "portId": "port-4", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + ] + }, + "note": "", + "canonical": "{\"batchId\":\"batch-41\",\"controls\":[{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-1\"},{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-2\"},{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-3\"},{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-4\"}]}", + "digest": "a174e6849f0f02a4c160af2e487517f18bee3e58f3f0a7fa33a66afe5904df77" + }, + { + "name": "step result", + "type": "StepResult", + "value": { + "batchId": "batch-41", + "appliedFromStep": "41", + "nextStep": "42", + "appliedControlsDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "observation": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "note": "", + "canonical": "{\"appliedControlsDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"appliedFromStep\":\"41\",\"batchId\":\"batch-41\",\"nextStep\":\"42\",\"observation\":{\"audio\":[{\"discontinuity\":false,\"firstSample\":\"33600\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}],\"boundary\":\"42\",\"broadcastViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"engineFrame\":\"1\",\"inspection\":{\"schema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"value\":{\"map\":40,\"x\":5,\"y\":7}},\"sensoryViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"worldTime\":{\"denominator\":\"3\",\"numerator\":\"50000000\"}}}", + "digest": "afabd9118523470ac7ccc4caa8bb907b0bd6b79202b22c3d1cec0eaf33855e8f" + }, + { + "name": "hello params", + "type": "HelloParams", + "value": { + "sessionId": "demo", + "expectedWorkerId": "fly-a", + "role": "agent", + "supportedMajors": [ + 1 + ] + }, + "note": "", + "canonical": "{\"expectedWorkerId\":\"fly-a\",\"role\":\"agent\",\"sessionId\":\"demo\",\"supportedMajors\":[1]}", + "digest": "d1d183576de14e9a4f3a247e86b467520f5ee48b861ff1f78c2e21aa6c65acb4" + }, + { + "name": "hello result for an agent", + "type": "HelloResult", + "value": { + "selectedMajor": 1, + "selectedMinor": 0, + "workerId": "fly-a", + "incarnationId": "inc-1", + "role": "agent", + "buildDigest": "44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66", + "contractDigest": "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf", + "capabilities": [ + "agent-step-v1", + "checkpoint-v1", + "pixel-observation-v1" + ], + "limits": { + "maxAgents": 4, + "maxPorts": 4 + } + }, + "note": "", + "canonical": "{\"buildDigest\":\"44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66\",\"capabilities\":[\"agent-step-v1\",\"checkpoint-v1\",\"pixel-observation-v1\"],\"contractDigest\":\"cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf\",\"incarnationId\":\"inc-1\",\"limits\":{\"maxAgents\":4,\"maxPorts\":4},\"role\":\"agent\",\"selectedMajor\":1,\"selectedMinor\":0,\"workerId\":\"fly-a\"}", + "digest": "9f8cbc9dfe7a7532c2e41b53c4ce001973ca95703a81aad30ee2b1c8b640bc13" + }, + { + "name": "hello result for an environment", + "type": "HelloResult", + "value": { + "selectedMajor": 1, + "selectedMinor": 0, + "workerId": "world", + "incarnationId": "inc-2", + "role": "environment", + "buildDigest": "44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66", + "contractDigest": "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf", + "capabilities": [ + "world-step-v1", + "checkpoint-v1" + ], + "limits": { + "maxAgents": 1, + "maxPorts": 1 + } + }, + "note": "", + "canonical": "{\"buildDigest\":\"44575cf5b28512d75644bf54a517dcef304ff809fd511747621b4d64f19aac66\",\"capabilities\":[\"world-step-v1\",\"checkpoint-v1\"],\"contractDigest\":\"cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf\",\"incarnationId\":\"inc-2\",\"limits\":{\"maxAgents\":1,\"maxPorts\":1},\"role\":\"environment\",\"selectedMajor\":1,\"selectedMinor\":0,\"workerId\":\"world\"}", + "digest": "0dc33a6cbf6bf66c4d28dc41b17b2eb7918b55c65469f4161e009f662f11bca2" + }, + { + "name": "status result before initialization", + "type": "StatusResult", + "value": { + "state": "uninitialized", + "currentScope": null, + "activeRequestId": null, + "lastCompletedRequestId": null, + "lastBatchId": null, + "progressCounter": "0" + }, + "note": "", + "canonical": "{\"activeRequestId\":null,\"currentScope\":null,\"lastBatchId\":null,\"lastCompletedRequestId\":null,\"progressCounter\":\"0\",\"state\":\"uninitialized\"}", + "digest": "3c3fccc7e3f27a83bcadfd5cc081861039edd0845001c9dc5484d4b6042a1597" + }, + { + "name": "status result while advancing", + "type": "StatusResult", + "value": { + "state": "advancing", + "currentScope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "activeRequestId": "req-41", + "lastCompletedRequestId": "req-40", + "lastBatchId": "batch-41", + "progressCounter": "9001" + }, + "note": "", + "canonical": "{\"activeRequestId\":\"req-41\",\"currentScope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"},\"lastBatchId\":\"batch-41\",\"lastCompletedRequestId\":\"req-40\",\"progressCounter\":\"9001\",\"state\":\"advancing\"}", + "digest": "7685d8f26c9ac05e98b31e49623482473ce6562e7312fe7a07ad2d0140885442" + }, + { + "name": "acknowledge params", + "type": "AcknowledgeParams", + "value": { + "requestIds": [ + "req-1", + "req-2" + ] + }, + "note": "", + "canonical": "{\"requestIds\":[\"req-1\",\"req-2\"]}", + "digest": "ecdd5329b6c03e60e4baa506410e49631cdc6c6bc69b91c4a587e990c1bc2d0b" + }, + { + "name": "acknowledge params at the cap", + "type": "AcknowledgeParams", + "value": { + "requestIds": [ + "req-1", + "req-2", + "req-3", + "req-4", + "req-5", + "req-6", + "req-7", + "req-8", + "req-9", + "req-10", + "req-11", + "req-12", + "req-13", + "req-14", + "req-15", + "req-16" + ] + }, + "note": "", + "canonical": "{\"requestIds\":[\"req-1\",\"req-2\",\"req-3\",\"req-4\",\"req-5\",\"req-6\",\"req-7\",\"req-8\",\"req-9\",\"req-10\",\"req-11\",\"req-12\",\"req-13\",\"req-14\",\"req-15\",\"req-16\"]}", + "digest": "4f1bceb404d3fc6dce13ad4cd9fbb013b3d86a9c4ee9d6eaf0c333ee5d2f9a95" + }, + { + "name": "acknowledge result dropping unknown ids", + "type": "AcknowledgeResult", + "value": { + "acknowledged": [ + "req-1" + ] + }, + "note": "", + "canonical": "{\"acknowledged\":[\"req-1\"]}", + "digest": "ef54663300ad31be91aac4d9fe3a30a8a043287865fe775ada4789dd4ce2c294" + }, + { + "name": "shutdown params", + "type": "ShutdownParams", + "value": { + "reason": "operator-request" + }, + "note": "", + "canonical": "{\"reason\":\"operator-request\"}", + "digest": "a69c521cf80c05893c9972bf7929c6ec1733314ece599bc6e650247adf74ec2b" + }, + { + "name": "shutdown result", + "type": "ShutdownResult", + "value": { + "stopping": true + }, + "note": "", + "canonical": "{\"stopping\":true}", + "digest": "345f7d009561b6b78b4a89f1a3e5aa15d7b3249c9037cfbaa61088deaae4ed3f" + }, + { + "name": "task event for a transition", + "type": "TaskEvent", + "value": { + "id": "evt-1", + "kindId": "badge", + "sourceStep": "42", + "agentId": "fly-a", + "payload": { + "schema": { + "id": "pokemon.badge.v1", + "version": 1, + "digest": "8d3c215ae3088736279f7cd130ff173ed57101a729629983c553246dc9bd44db" + }, + "value": { + "badge": 2 + } + } + }, + "note": "", + "canonical": "{\"agentId\":\"fly-a\",\"id\":\"evt-1\",\"kindId\":\"badge\",\"payload\":{\"schema\":{\"digest\":\"8d3c215ae3088736279f7cd130ff173ed57101a729629983c553246dc9bd44db\",\"id\":\"pokemon.badge.v1\",\"version\":1},\"value\":{\"badge\":2}},\"sourceStep\":\"42\"}", + "digest": "e4e879b453bce7b5560e6e31412b8e30b4ae5ad5baef4aaacd86fe64c72c61ed" + }, + { + "name": "task event with no recipient", + "type": "TaskEvent", + "value": { + "id": "evt-2", + "kindId": "bootstrap", + "sourceStep": "0", + "agentId": null, + "payload": { + "schema": { + "id": "pokemon.bootstrap.v1", + "version": 1, + "digest": "88537024c85058bd9d01546dada047a39d341811d7e74b47fdcfceae728cb20d" + }, + "value": {} + } + }, + "note": "", + "canonical": "{\"agentId\":null,\"id\":\"evt-2\",\"kindId\":\"bootstrap\",\"payload\":{\"schema\":{\"digest\":\"88537024c85058bd9d01546dada047a39d341811d7e74b47fdcfceae728cb20d\",\"id\":\"pokemon.bootstrap.v1\",\"version\":1},\"value\":{}},\"sourceStep\":\"0\"}", + "digest": "f7b2de586eb822ea82a59380188478066717d51319cdb65d913e5d12f4fd5402" + }, + { + "name": "terminal episode request", + "type": "EpisodeRequest", + "value": { + "kind": "terminal", + "reason": "game-over", + "outcome": { + "schema": { + "id": "pokemon.outcome.v1", + "version": 1, + "digest": "ca50728d52392393703acff8c15d1d40e0bf3c65d5eb2686037923a8c503a425" + }, + "value": { + "rank": 10 + } + } + }, + "note": "", + "canonical": "{\"kind\":\"terminal\",\"outcome\":{\"schema\":{\"digest\":\"ca50728d52392393703acff8c15d1d40e0bf3c65d5eb2686037923a8c503a425\",\"id\":\"pokemon.outcome.v1\",\"version\":1},\"value\":{\"rank\":10}},\"reason\":\"game-over\"}", + "digest": "7bf1b49d1144d4cc9c8090e4f74fc7067ab7ac89a428f2cb4d39e97cb8e7df30" + }, + { + "name": "view descriptor", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + }, + "note": "", + "canonical": "{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}", + "digest": "53979af7b85fd859eefb4724fc01f40e6d89bfa3f7c602532a9148ccf6e1df73" + }, + { + "name": "view descriptor with a two step pipeline delay", + "type": "ViewDescriptor", + "value": { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 2 + }, + "note": "", + "canonical": "{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":2,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}", + "digest": "72454a1263266ec7660b78bb3df4f94cd13ec90813a282a57044182c80151498" + }, + { + "name": "view reference", + "type": "ViewRef", + "value": { + "viewId": "screen", + "producedStep": "41", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + }, + "note": "", + "canonical": "{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"41\",\"viewId\":\"screen\"}", + "digest": "0e480b869f260e96dba893b9cb836cb4c98de67f1fbd7cd47d78c3be93a81efa" + }, + { + "name": "audio descriptor", + "type": "AudioDescriptor", + "value": { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + }, + "note": "", + "canonical": "{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}", + "digest": "0c04ad89aae871a1210e603abd4af011fe05e0b94284f41927eaa240e03b9a3c" + }, + { + "name": "audio reference", + "type": "AudioRef", + "value": { + "streamId": "mix", + "firstSample": "0", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + }, + "note": "", + "canonical": "{\"discontinuity\":false,\"firstSample\":\"0\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}", + "digest": "0b3ae931f6c9b8854203a5aa17370cc36f9dff27bcfdb49e33edcb670b546cba" + }, + { + "name": "audio reference marking a discontinuity", + "type": "AudioRef", + "value": { + "streamId": "mix", + "firstSample": "48000", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": true + }, + "note": "", + "canonical": "{\"discontinuity\":true,\"firstSample\":\"48000\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}", + "digest": "e1ef310a3f4e93f392e36354d376701cf9a407f9fc56c0d5f52bfd15f3b3ea1f" + }, + { + "name": "empty audio chunk", + "type": "AudioRef", + "value": { + "streamId": "mix", + "firstSample": "0", + "sampleFrames": 0, + "samples": { + "storeId": "store-1", + "artifactId": "audio-empty", + "generation": "1", + "byteLength": "0", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": true + }, + "note": "", + "canonical": "{\"discontinuity\":true,\"firstSample\":\"0\",\"sampleFrames\":0,\"samples\":{\"artifactId\":\"audio-empty\",\"byteLength\":\"0\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}", + "digest": "0ff6dcec5a76aa3e6347026f05704c34207c39dbe8f582c7ad329ac1cd3aba57" + }, + { + "name": "capture params", + "type": "CaptureParams", + "value": { + "checkpointId": "ckpt-1" + }, + "note": "", + "canonical": "{\"checkpointId\":\"ckpt-1\"}", + "digest": "0840127e4277d0eac4d69dd88468c7737d0a8b64aad12ef554a25e4a7c653baa" + }, + { + "name": "capture result", + "type": "CaptureResult", + "value": { + "checkpointId": "ckpt-1", + "boundary": "42", + "compatibilityDigest": "2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d", + "payload": { + "storeId": "store-1", + "artifactId": "payload-1", + "generation": "1", + "byteLength": "1024", + "contentType": "application/octet-stream", + "digest": "239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5" + } + }, + "note": "", + "canonical": "{\"boundary\":\"42\",\"checkpointId\":\"ckpt-1\",\"compatibilityDigest\":\"2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d\",\"payload\":{\"artifactId\":\"payload-1\",\"byteLength\":\"1024\",\"contentType\":\"application/octet-stream\",\"digest\":\"239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5\",\"generation\":\"1\",\"storeId\":\"store-1\"}}", + "digest": "73ef46de54c10ca8a28bb9c71aecb25199c995027af7271ca4bad9d2540a61c3" + }, + { + "name": "stage restore params", + "type": "StageRestoreParams", + "value": { + "checkpointId": "ckpt-1", + "sourceScope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "compatibilityDigest": "2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d", + "payload": { + "storeId": "store-1", + "artifactId": "payload-2", + "generation": "1", + "byteLength": "1024", + "contentType": "application/octet-stream", + "digest": "239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5" + } + }, + "note": "", + "canonical": "{\"checkpointId\":\"ckpt-1\",\"compatibilityDigest\":\"2b33a9570d42f8af1c84dbe8b8c9bb50664f9b19ca8f7ee100548b9945a52f5d\",\"payload\":{\"artifactId\":\"payload-2\",\"byteLength\":\"1024\",\"contentType\":\"application/octet-stream\",\"digest\":\"239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5\",\"generation\":\"1\",\"storeId\":\"store-1\"},\"sourceScope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"42\"}}", + "digest": "da479328a30068075fd8702cd53e06e2aaecd8ddc9b015998ee7901e8a2dcab0" + }, + { + "name": "stage restore result", + "type": "StageRestoreResult", + "value": { + "checkpointId": "ckpt-1", + "restoreToken": "restore-1" + }, + "note": "", + "canonical": "{\"checkpointId\":\"ckpt-1\",\"restoreToken\":\"restore-1\"}", + "digest": "006d551d31d2cb4a6d850a8bb4f19ba16b4173835fda5eda8f6806cb03ec163b" + }, + { + "name": "activate restore params", + "type": "ActivateRestoreParams", + "value": { + "restoreToken": "restore-1" + }, + "note": "", + "canonical": "{\"restoreToken\":\"restore-1\"}", + "digest": "fadaacb907911c4402937640c711d3368bfe348842916856d34750795bad6ee3" + }, + { + "name": "activate restore result for an agent", + "type": "ActivateRestoreResult", + "value": { + "committedStep": "42", + "checkpointId": "ckpt-1", + "observation": null + }, + "note": "", + "canonical": "{\"checkpointId\":\"ckpt-1\",\"committedStep\":\"42\",\"observation\":null}", + "digest": "eec98a58c81ad50c3dcd4406c743c7139e60108869dedbfd6d55a2f037bcd41a" + }, + { + "name": "activate restore result for an environment", + "type": "ActivateRestoreResult", + "value": { + "committedStep": "42", + "checkpointId": "ckpt-1", + "observation": { + "boundary": "42", + "worldTime": { + "numerator": "50000000", + "denominator": "3" + }, + "engineFrame": "1", + "sensoryViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "inspection": { + "schema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "value": { + "map": 40, + "x": 5, + "y": 7 + } + }, + "broadcastViews": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + } + }, + "note": "", + "canonical": "{\"checkpointId\":\"ckpt-1\",\"committedStep\":\"42\",\"observation\":{\"audio\":[{\"discontinuity\":false,\"firstSample\":\"33600\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}],\"boundary\":\"42\",\"broadcastViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"engineFrame\":\"1\",\"inspection\":{\"schema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"value\":{\"map\":40,\"x\":5,\"y\":7}},\"sensoryViews\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"worldTime\":{\"denominator\":\"3\",\"numerator\":\"50000000\"}}}", + "digest": "90950aaf244f6f779268687db63b69c3cb1ee316de2541fcf0b1fa393a34b819" + }, + { + "name": "session descriptor", + "type": "SessionDescriptor", + "value": { + "sessionId": "demo", + "revision": "7", + "compositionDigest": "730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0", + "schedulerId": "lockstep-v1", + "environment": { + "backendDigest": "10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be", + "contentDigest": "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", + "configurationDigest": "b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1", + "stepDuration": { + "numerator": "50000000", + "denominator": "3" + }, + "ports": [ + { + "portId": "port-1", + "controls": { + "schema": { + "id": "gameboy.controller.v1", + "version": 1, + "digest": "b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5" + }, + "buttons": [ + "a", + "b", + "start", + "select", + "up", + "down", + "left", + "right" + ], + "axes": [ + { + "id": "stick-x", + "range": "bipolar", + "neutral": 0.0 + }, + { + "id": "trigger", + "range": "unit", + "neutral": 0.0 + } + ] + } + } + ], + "inspectionSchema": { + "id": "gameboy.inspection.v1", + "version": 1, + "digest": "1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1" + }, + "views": [ + { + "viewId": "screen", + "width": 160, + "height": 144, + "format": "rgba8", + "rowStride": 640, + "pixelAspect": { + "numerator": 10, + "denominator": 11 + }, + "observationDelaySteps": 0 + } + ], + "audio": [ + { + "streamId": "mix", + "sampleRate": 48000, + "channels": 2, + "format": "f32le-interleaved" + } + ], + "recovery": "exact-checkpoint", + "determinism": "fixed-build" + }, + "taskSchema": { + "id": "pokemon.task.v1", + "version": 1, + "digest": "e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4" + }, + "agents": [ + { + "agentId": "fly-a", + "portId": "port-1", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "datasetDigest": "6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52", + "indexDigest": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "neuronCount": "139255", + "rateRoles": [ + "kenyon", + "mbon" + ], + "supportedStimuli": [ + "sugar" + ] + } + ], + "assets": [ + { + "id": "profile-fly-a", + "digest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "byteLength": "4096", + "format": "flyprofile" + } + ] + }, + "note": "", + "canonical": "{\"agents\":[{\"agentId\":\"fly-a\",\"datasetDigest\":\"6c0af1f0784ef63a393ee77d614e8246c625051360f3f1a48838374c5d355b52\",\"indexDigest\":\"1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6\",\"neuronCount\":\"139255\",\"portId\":\"port-1\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"rateRoles\":[\"kenyon\",\"mbon\"],\"supportedStimuli\":[\"sugar\"]}],\"assets\":[{\"byteLength\":\"4096\",\"digest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"format\":\"flyprofile\",\"id\":\"profile-fly-a\"}],\"compositionDigest\":\"730d725c8a59d3a7303def2bed041a577edb4255aabd4889ce12918311d952f0\",\"environment\":{\"audio\":[{\"channels\":2,\"format\":\"f32le-interleaved\",\"sampleRate\":48000,\"streamId\":\"mix\"}],\"backendDigest\":\"10e08a419e850eba1ebba18fdd28eb7ec1b7e8baa9bcc3b973e2b8891ec726be\",\"configurationDigest\":\"b7d64a9221007dfd5390f7df6cd5b8f3ea4f82faa1237141e35ebf161f5511a1\",\"contentDigest\":\"ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73\",\"determinism\":\"fixed-build\",\"inspectionSchema\":{\"digest\":\"1883cee6532d0bfbb28854b82db8450d525c2e4fcec01567b4aa6d99917eafb1\",\"id\":\"gameboy.inspection.v1\",\"version\":1},\"ports\":[{\"controls\":{\"axes\":[{\"id\":\"stick-x\",\"neutral\":0,\"range\":\"bipolar\"},{\"id\":\"trigger\",\"neutral\":0,\"range\":\"unit\"}],\"buttons\":[\"a\",\"b\",\"start\",\"select\",\"up\",\"down\",\"left\",\"right\"],\"schema\":{\"digest\":\"b1df9d20fe1deaeb52f17cf261bf728b28143c777878b96cacfc4826903a48d5\",\"id\":\"gameboy.controller.v1\",\"version\":1}},\"portId\":\"port-1\"}],\"recovery\":\"exact-checkpoint\",\"stepDuration\":{\"denominator\":\"3\",\"numerator\":\"50000000\"},\"views\":[{\"format\":\"rgba8\",\"height\":144,\"observationDelaySteps\":0,\"pixelAspect\":{\"denominator\":11,\"numerator\":10},\"rowStride\":640,\"viewId\":\"screen\",\"width\":160}]},\"revision\":\"7\",\"schedulerId\":\"lockstep-v1\",\"sessionId\":\"demo\",\"taskSchema\":{\"digest\":\"e4ca46e969a0f0e53a4f537561360851269e0b3f684eccaa634fe9672fdaabb4\",\"id\":\"pokemon.task.v1\",\"version\":1}}", + "digest": "58857f4d64c19fdda320e8b3f857c6964d12748d01b7ba5a4fd40fc4393529e8" + }, + { + "name": "committed snapshot at boundary zero", + "type": "CommittedSnapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "0" + }, + "episodeId": "episode-1", + "sequence": "0", + "worldTime": { + "numerator": "0", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2500", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": null, + "appliedControls": null + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 0 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "0", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [] + }, + "eventIds": [] + }, + "note": "", + "canonical": "{\"agents\":[{\"agentId\":\"fly-a\",\"appliedControls\":null,\"selectedDecision\":null,\"telemetry\":{\"brainTicks\":\"2500\",\"learning\":{\"changed\":\"2\",\"enabled\":true,\"signal\":0.5,\"updates\":\"4\"},\"populationRateHz\":12.5,\"rates\":[{\"hz\":3.25,\"roleId\":\"kenyon\"},{\"hz\":0,\"roleId\":\"mbon\"}]}}],\"descriptorRevision\":\"7\",\"episodeId\":\"episode-1\",\"eventIds\":[],\"media\":{\"audio\":[],\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"0\",\"viewId\":\"screen\"}]},\"progress\":{\"schema\":{\"digest\":\"80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1\",\"id\":\"pokemon.progress.v1\",\"version\":1},\"value\":{\"rank\":0}},\"publisherIncarnation\":\"pub-1\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"0\"},\"sequence\":\"0\",\"worldTime\":{\"denominator\":\"1\",\"numerator\":\"0\"}}", + "digest": "e9c432eee00e95c28d8a12428aaa9cb71568bcaf079b557b01fb1fde26723da9" + }, + { + "name": "committed snapshot after a transition", + "type": "CommittedSnapshot", + "value": { + "descriptorRevision": "7", + "publisherIncarnation": "pub-1", + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "42" + }, + "episodeId": "episode-1", + "sequence": "42", + "worldTime": { + "numerator": "700000000", + "denominator": "1" + }, + "agents": [ + { + "agentId": "fly-a", + "telemetry": { + "brainTicks": "2534", + "populationRateHz": 12.5, + "rates": [ + { + "roleId": "kenyon", + "hz": 3.25 + }, + { + "roleId": "mbon", + "hz": 0.0 + } + ], + "learning": { + "enabled": true, + "updates": "4", + "changed": "2", + "signal": 0.5 + } + }, + "selectedDecision": { + "schema": { + "id": "gameboy.intent.v1", + "version": 1, + "digest": "e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d" + }, + "value": { + "press": "a" + } + }, + "appliedControls": { + "portId": "port-1", + "buttons": [ + { + "id": "a", + "down": true + }, + { + "id": "b", + "down": false + }, + { + "id": "start", + "down": false + }, + { + "id": "select", + "down": false + }, + { + "id": "up", + "down": false + }, + { + "id": "down", + "down": false + }, + { + "id": "left", + "down": false + }, + { + "id": "right", + "down": false + } + ], + "axes": [ + { + "id": "stick-x", + "value": 0.0 + }, + { + "id": "trigger", + "value": 0.0 + } + ] + } + } + ], + "progress": { + "schema": { + "id": "pokemon.progress.v1", + "version": 1, + "digest": "80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1" + }, + "value": { + "rank": 10 + } + }, + "media": { + "views": [ + { + "viewId": "screen", + "producedStep": "42", + "pixels": { + "storeId": "store-1", + "artifactId": "frame-1", + "generation": "1", + "byteLength": "92160", + "contentType": "image/x-rgba8", + "digest": null + } + } + ], + "audio": [ + { + "streamId": "mix", + "firstSample": "33600", + "sampleFrames": 800, + "samples": { + "storeId": "store-1", + "artifactId": "audio-1", + "generation": "1", + "byteLength": "6400", + "contentType": "audio/x-f32le", + "digest": null + }, + "discontinuity": false + } + ] + }, + "eventIds": [ + "evt-1" + ] + }, + "note": "", + "canonical": "{\"agents\":[{\"agentId\":\"fly-a\",\"appliedControls\":{\"axes\":[{\"id\":\"stick-x\",\"value\":0},{\"id\":\"trigger\",\"value\":0}],\"buttons\":[{\"down\":true,\"id\":\"a\"},{\"down\":false,\"id\":\"b\"},{\"down\":false,\"id\":\"start\"},{\"down\":false,\"id\":\"select\"},{\"down\":false,\"id\":\"up\"},{\"down\":false,\"id\":\"down\"},{\"down\":false,\"id\":\"left\"},{\"down\":false,\"id\":\"right\"}],\"portId\":\"port-1\"},\"selectedDecision\":{\"schema\":{\"digest\":\"e70451b26fb5462ec63729d8355912dad01c3963a95bec0e6a8452375717ad9d\",\"id\":\"gameboy.intent.v1\",\"version\":1},\"value\":{\"press\":\"a\"}},\"telemetry\":{\"brainTicks\":\"2534\",\"learning\":{\"changed\":\"2\",\"enabled\":true,\"signal\":0.5,\"updates\":\"4\"},\"populationRateHz\":12.5,\"rates\":[{\"hz\":3.25,\"roleId\":\"kenyon\"},{\"hz\":0,\"roleId\":\"mbon\"}]}}],\"descriptorRevision\":\"7\",\"episodeId\":\"episode-1\",\"eventIds\":[\"evt-1\"],\"media\":{\"audio\":[{\"discontinuity\":false,\"firstSample\":\"33600\",\"sampleFrames\":800,\"samples\":{\"artifactId\":\"audio-1\",\"byteLength\":\"6400\",\"contentType\":\"audio/x-f32le\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"streamId\":\"mix\"}],\"views\":[{\"pixels\":{\"artifactId\":\"frame-1\",\"byteLength\":\"92160\",\"contentType\":\"image/x-rgba8\",\"digest\":null,\"generation\":\"1\",\"storeId\":\"store-1\"},\"producedStep\":\"42\",\"viewId\":\"screen\"}]},\"progress\":{\"schema\":{\"digest\":\"80fa973157f334bb3208dfff6f7a2f298e79f73f07c72c33f5725975c6cff8e1\",\"id\":\"pokemon.progress.v1\",\"version\":1},\"value\":{\"rank\":10}},\"publisherIncarnation\":\"pub-1\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"42\"},\"sequence\":\"42\",\"worldTime\":{\"denominator\":\"1\",\"numerator\":\"700000000\"}}", + "digest": "d5ed83ccb866318b3cf17b53c8a5b1dea2f404b10649f6b640bf5f2b88a31435" + }, + { + "name": "transition trace", + "type": "TransitionTrace", + "value": { + "behaviour": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + } + ], + "outcomeIds": [ + "outcome-1" + ], + "eventIds": [ + "evt-1" + ], + "publishedBoundary": "42" + }, + "operational": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + } + }, + "note": "", + "canonical": "{\"behaviour\":{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}},\"operational\":{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}}", + "digest": "707701d6dcf5529a67cc9b1e300d5e1a273b9a7b02d91154d4a2a1978cd8e549" + }, + { + "name": "trace behaviour on its own", + "type": "TraceBehaviour", + "value": { + "scope": { + "sessionId": "demo", + "epoch": "epoch-1", + "step": "41" + }, + "agents": [ + { + "agentId": "fly-a", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2517", + "remainder": { + "numerator": "1000000", + "denominator": "3" + }, + "decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b", + "committedStep": "42" + }, + { + "agentId": "fly-b", + "profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0", + "ticksAdvanced": "17", + "brainTicks": "2550", + "remainder": { + "numerator": "0", + "denominator": "1" + }, + "decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e", + "committedStep": "42" + } + ], + "batchId": "batch-41", + "controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6", + "acknowledgedBoundary": "42", + "observationBoundaries": [ + { + "viewId": "screen", + "producedStep": "42" + } + ], + "outcomeIds": [ + "outcome-1" + ], + "eventIds": [ + "evt-1" + ], + "publishedBoundary": "42" + }, + "note": "the half two runs must agree on", + "canonical": "{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}}", + "digest": "d938188fe3006537dbc0038fabf2a05df584e64b361b1106d79f3f965dff414a" + }, + { + "name": "trace operational metadata on its own", + "type": "TraceOperational", + "value": { + "wallTimeNs": "1234567890", + "prepareRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-41" + }, + { + "agentId": "fly-b", + "requestId": "req-42" + } + ], + "advanceRequestId": "req-43", + "commitRequestIds": [ + { + "agentId": "fly-a", + "requestId": "req-44" + }, + { + "agentId": "fly-b", + "requestId": "req-45" + } + ], + "busCallIds": [ + "call-100", + "call-101", + "call-102" + ], + "deliveryIds": [ + "dlv-7", + "own-9" + ] + }, + "note": "recorded by step-v1 section 8, excluded from its comparison", + "canonical": "{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}", + "digest": "07c3402ab0e48b186d56f667da980a15b2a48b21381235cff37523a24ff0dfff" + } + ] +} diff --git a/services/flysim/crates/fly-session-types/tests/canonical_json.rs b/services/flysim/crates/fly-session-types/tests/canonical_json.rs new file mode 100644 index 0000000..bd289dc --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/canonical_json.rs @@ -0,0 +1,192 @@ +//! Canonical JSON (RFC 8785) and the digest rules of ipc-v1 section 5. + +use fly_session_types::scalar::{DomainType, Scope}; +use fly_session_types::{canonical, fixtures}; +use serde_json::{Value, json}; + +#[test] +fn object_keys_are_sorted_by_utf16_code_unit() { + let value = json!({"b": 1, "a": 2, "A": 3, "\u{00e9}": 4, "\u{10400}": 5, "\u{ff21}": 6}); + assert_eq!( + canonical::canonicalize(&value).expect("canonicalizable"), + "{\"A\":3,\"a\":2,\"b\":1,\"\u{00e9}\":4,\"\u{10400}\":5,\"\u{ff21}\":6}", + "keys sort by UTF-16 code unit, so an astral key (leading surrogate D801) sorts \ + before U+FF21, which is where a JavaScript string sort puts it too and where a sort \ + by Unicode code point would not" + ); +} + +#[test] +fn numbers_print_the_way_ecmascript_prints_them() { + let file = fixtures::load("boundaries.json").expect("boundaries.json"); + for case in file.get("doubles").and_then(Value::as_array).expect("doubles") { + let value = case.get("value").expect("value"); + let accept = case.get("accept").and_then(Value::as_bool).expect("accept"); + let outcome = canonical::canonicalize(value); + assert_eq!( + outcome.is_ok(), + accept, + "{value}: {}", + fixtures::field(case, "reason").unwrap_or("") + ); + if let (Ok(text), Some(expected)) = (outcome, case.get("canonical").and_then(Value::as_str)) + { + assert_eq!(text, expected, "{value} must print as {expected}"); + } + } +} + +#[test] +fn strings_are_escaped_the_way_json_stringify_escapes_them() { + let value = json!({"s": "quote \" backslash \\ tab \t newline \n bell \u{7} del \u{7f} e\u{301}"}); + assert_eq!( + canonical::canonicalize(&value).expect("canonicalizable"), + "{\"s\":\"quote \\\" backslash \\\\ tab \\t newline \\n bell \\u0007 del \u{7f} e\u{301}\"}", + "only the escapes JSON.stringify emits, with lowercase hex" + ); +} + +#[test] +fn canonical_form_does_not_depend_on_the_input_formatting() { + let compact = br#"{"b":[1,2,{"y":true,"x":null}],"a":"z"}"#; + let pretty = br#"{ + "a" : "z", + "b": [ 1, 2, { "x": null, "y": true } ] + }"#; + let left = canonical::parse_strict(compact).expect("parses"); + let right = canonical::parse_strict(pretty).expect("parses"); + assert_eq!( + canonical::canonicalize(&left).expect("canonicalizable"), + canonical::canonicalize(&right).expect("canonicalizable") + ); + assert_eq!( + canonical::digest_of(&left).expect("digest"), + canonical::digest_of(&right).expect("digest"), + "whitespace and key order are not content" + ); +} + +#[test] +fn duplicate_keys_and_invalid_utf8_never_parse() { + assert!(canonical::parse_strict(br#"{"a":1,"a":2}"#).is_err()); + assert!(canonical::parse_strict(b"{\"a\":\"\xff\"}").is_err()); + assert!(canonical::parse_strict(br#"{"a":1} {"b":2}"#).is_err()); + assert!(canonical::parse_strict(br#"{"a":NaN}"#).is_err()); +} + +#[test] +fn an_envelope_over_64_kib_is_refused() { + let big = json!({"pad": "a".repeat(canonical::MAX_ENVELOPE_BYTES)}); + assert!(canonical::require_envelope_fit(&big, 0).is_err()); + let small = json!({"pad": "a"}); + let length = canonical::canonicalize(&small).expect("canonicalizable").len(); + assert_eq!( + canonical::require_envelope_fit(&small, canonical::MAX_ENVELOPE_BYTES - length) + .expect("fits exactly"), + canonical::MAX_ENVELOPE_BYTES + ); + assert!( + canonical::require_envelope_fit(&small, canonical::MAX_ENVELOPE_BYTES - length + 1) + .is_err(), + "one byte past the ceiling is refused" + ); +} + +#[test] +fn operation_keys_match_the_fixture_and_separate_the_operations_they_should() { + let file = fixtures::load("operations.json").expect("operations.json"); + let mut digests: Vec<(String, String)> = Vec::new(); + for case in file.get("keys").and_then(Value::as_array).expect("keys") { + let name = fixtures::field(case, "name").expect("name"); + let scope = Scope::from_json(case.get("scope").expect("scope")).expect("scope"); + let method = fixtures::field(case, "method").expect("method"); + let worker = fixtures::field(case, "workerId").expect("workerId"); + let key = canonical::OperationKey::new(scope, method, worker).expect("a key"); + let digest = key.digest().expect("digest"); + assert_eq!( + digest, + fixtures::field(case, "digest").expect("digest"), + "{name}: operation key digest must match the fixture" + ); + digests.push((name.to_owned(), digest)); + } + for (index, (name, digest)) in digests.iter().enumerate() { + for (other_name, other) in &digests[index + 1..] { + assert_ne!( + digest, other, + "{name} and {other_name} are different operations" + ); + } + } +} + +#[test] +fn canonical_bodies_match_the_fixture() { + let file = fixtures::load("operations.json").expect("operations.json"); + for case in file.get("bodies").and_then(Value::as_array).expect("bodies") { + let name = fixtures::field(case, "name").expect("name"); + let method = fixtures::field(case, "method").expect("method"); + let scope = match case.get("scope") { + Some(Value::Null) | None => None, + Some(v) => Some(Scope::from_json(v).expect("scope")), + }; + let params = case.get("params").expect("params"); + assert_eq!( + canonical::body_digest(method, scope.as_ref(), params).expect("digest"), + fixtures::field(case, "digest").expect("digest"), + "{name}: canonical body digest must match the fixture" + ); + } +} + +/// The pairs that decide whether a duplicate is a safe replay or a CONFLICT. +#[test] +fn operation_pairs_agree_with_the_fixture_about_sameness() { + let file = fixtures::load("operations.json").expect("operations.json"); + for case in file.get("pairs").and_then(Value::as_array).expect("pairs") { + let name = fixtures::field(case, "name").expect("name"); + let reason = fixtures::field(case, "reason").expect("reason"); + let worker = fixtures::field(case, "workerId").expect("workerId"); + let right_worker = fixtures::field(case, "rightWorkerId").unwrap_or(worker); + let side = |key: &str, worker: &str| { + let value = case.get(key).expect("side"); + let method = fixtures::field(value, "method").expect("method"); + let scope = Scope::from_json(value.get("scope").expect("scope")).expect("scope"); + let params = value.get("params").expect("params"); + let key_digest = canonical::OperationKey::new(scope.clone(), method, worker) + .expect("a key") + .digest() + .expect("digest"); + let body = canonical::body_digest(method, Some(&scope), params).expect("digest"); + (key_digest, body) + }; + let (left_key, left_body) = side("left", worker); + let (right_key, right_body) = side("right", right_worker); + assert_eq!( + left_key == right_key, + case.get("sameKey").and_then(Value::as_bool).expect("sameKey"), + "{name}: operation key sameness. {reason}" + ); + assert_eq!( + left_body == right_body, + case.get("sameBody").and_then(Value::as_bool).expect("sameBody"), + "{name}: canonical body sameness. {reason}" + ); + } +} + +#[test] +fn a_domain_body_can_never_carry_a_bus_identity() { + let file = fixtures::load("operations.json").expect("operations.json"); + for case in file.get("rejected").and_then(Value::as_array).expect("rejected") { + let name = fixtures::field(case, "name").expect("name"); + let method = fixtures::field(case, "method").expect("method"); + let scope = Scope::from_json(case.get("scope").expect("scope")).expect("scope"); + let params = case.get("params").expect("params"); + assert!( + canonical::body_digest(method, Some(&scope), params).is_err(), + "{name} must be refused: {}", + fixtures::field(case, "reason").unwrap_or("") + ); + } +} diff --git a/services/flysim/crates/fly-session-types/tests/checkpoint_envelope.rs b/services/flysim/crates/fly-session-types/tests/checkpoint_envelope.rs new file mode 100644 index 0000000..868e502 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/checkpoint_envelope.rs @@ -0,0 +1,165 @@ +//! The `FLYSESS1` envelope layout: its fixture, its offsets and the corruptions it refuses. + +use fly_session_types::{canonical, checkpoint, fixtures}; +use serde_json::Value; + +fn envelope_bytes(file: &Value) -> Vec { + fixtures::decode_base64( + file.get("envelope") + .and_then(|e| e.get("base64")) + .and_then(Value::as_str) + .expect("base64"), + ) + .expect("base64") +} + +#[test] +fn the_fixture_envelope_decodes_to_its_recorded_layout() { + let file = fixtures::load("checkpoint-envelope.json").expect("checkpoint-envelope.json"); + let bytes = envelope_bytes(&file); + let envelope = checkpoint::decode(&bytes).expect("a valid envelope"); + checkpoint::validate_manifest(&envelope).expect("a complete manifest"); + + let layout = file["envelope"]["layout"].clone(); + assert_eq!(&bytes[0..8], checkpoint::MAGIC); + assert_eq!( + bytes.len().to_string(), + layout["totalBytes"].as_str().expect("totalBytes") + ); + assert_eq!( + envelope.layout.table_offset.to_string(), + layout["tableOffset"].as_str().expect("tableOffset") + ); + assert_eq!( + envelope.layout.manifest_bytes, + layout["manifestBytes"].as_u64().expect("manifestBytes") as u32 + ); + let entries = layout["entries"].as_array().expect("entries"); + assert_eq!(envelope.layout.entries.len(), entries.len()); + for (entry, recorded) in envelope.layout.entries.iter().zip(entries) { + assert_eq!(entry.name, recorded["name"].as_str().expect("name")); + assert_eq!( + entry.offset.to_string(), + recorded["offset"].as_str().expect("offset") + ); + assert_eq!( + entry.byte_length.to_string(), + recorded["byteLength"].as_str().expect("byteLength") + ); + assert_eq!( + checkpoint::hex(&entry.digest), + recorded["digest"].as_str().expect("digest") + ); + assert_eq!(entry.offset % 8, 0, "payloads start on an eight-byte boundary"); + } + + for payload in file["payloads"].as_array().expect("payloads") { + let name = payload["name"].as_str().expect("name"); + let expected = fixtures::decode_base64(payload["base64"].as_str().expect("base64")) + .expect("base64"); + assert_eq!( + envelope.payload(name).expect("a payload"), + expected.as_slice(), + "payload {name} must come back byte for byte" + ); + } + assert_eq!( + envelope.manifest, + file["manifest"], + "the manifest round trips as canonical JSON" + ); +} + +#[test] +fn every_recorded_corruption_is_refused() { + let file = fixtures::load("checkpoint-envelope.json").expect("checkpoint-envelope.json"); + let bytes = envelope_bytes(&file); + for case in file["corruption"].as_array().expect("corruption") { + let name = case["name"].as_str().expect("name"); + let offset = case["offset"].as_u64().expect("offset") as usize; + let mut corrupted = bytes.clone(); + corrupted[offset] ^= 0x01; + assert!( + checkpoint::decode(&corrupted).is_err(), + "{name} must be refused: {}", + case["reason"].as_str().unwrap_or("") + ); + } + let truncated = &bytes[..bytes.len() - 1]; + assert!( + checkpoint::decode(truncated).is_err(), + "a truncated envelope must be refused" + ); + assert!( + checkpoint::decode(&bytes[..8]).is_err(), + "a header alone is not an envelope" + ); +} + +#[test] +fn a_flysim01_envelope_is_not_read_as_a_session_checkpoint() { + // The historical envelope: magic, u32 manifest length, manifest, chunks, CRC32. + let mut legacy = Vec::new(); + legacy.extend_from_slice(b"FLYSIM01"); + let manifest = br#"{"schemaVersion":2,"chunks":[]}"#; + legacy.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); + legacy.extend_from_slice(manifest); + legacy.extend_from_slice(&0u32.to_le_bytes()); + assert!( + checkpoint::decode(&legacy).is_err(), + "FLYSESS1 is a new format; the old reader stays separate" + ); +} + +#[test] +fn the_layout_is_deterministic_and_the_manifest_is_canonical() { + let manifest = canonical::parse_strict(br#"{"b":2,"a":1}"#).expect("parses"); + let payloads = vec![ + ("one".to_owned(), b"first".to_vec()), + ("two".to_owned(), vec![0u8; 9]), + ]; + let bytes = checkpoint::encode(&manifest, &payloads).expect("encode"); + let again = checkpoint::encode(&manifest, &payloads).expect("encode"); + assert_eq!(bytes, again, "the same inputs produce the same bytes"); + let envelope = checkpoint::decode(&bytes).expect("decode"); + let start = checkpoint::HEADER_BYTES; + let end = start + envelope.layout.manifest_bytes as usize; + assert_eq!( + std::str::from_utf8(&bytes[start..end]).expect("utf-8"), + "{\"a\":1,\"b\":2}", + "the manifest is stored as canonical JSON" + ); + assert_eq!(envelope.layout.entries[1].offset % 8, 0); + assert!( + checkpoint::encode( + &manifest, + &[("one".to_owned(), vec![]), ("one".to_owned(), vec![])] + ) + .is_err(), + "payload names are unique" + ); + assert!( + checkpoint::encode(&manifest, &[("One".to_owned(), vec![])]).is_err(), + "payload names are Ids, and the widening from letters-only is deliberate, not arbitrary" + ); + assert!( + checkpoint::encode(&manifest, &[("a".to_owned(), Vec::new())]).is_ok(), + "an empty payload is still a payload" + ); +} + +#[test] +fn a_manifest_missing_a_required_field_is_not_a_complete_checkpoint() { + let file = fixtures::load("checkpoint-envelope.json").expect("checkpoint-envelope.json"); + let full = file["manifest"].clone(); + for field in checkpoint::REQUIRED_MANIFEST_FIELDS { + let mut manifest = full.clone(); + manifest.as_object_mut().expect("object").remove(*field); + let bytes = checkpoint::encode(&manifest, &[]).expect("encode"); + let envelope = checkpoint::decode(&bytes).expect("decode"); + assert!( + checkpoint::validate_manifest(&envelope).is_err(), + "a manifest without {field:?} must be refused" + ); + } +} diff --git a/services/flysim/crates/fly-session-types/tests/common/mod.rs b/services/flysim/crates/fly-session-types/tests/common/mod.rs new file mode 100644 index 0000000..3d2458e --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/common/mod.rs @@ -0,0 +1,133 @@ +//! One place that knows how to read every type named by a fixture. + +use flybus::wire::WireError; +use fly_session_types::media::*; +use fly_session_types::publishing::*; +use fly_session_types::rpc::*; +use fly_session_types::scalar::*; +use fly_session_types::trace::*; +use fly_session_types::workers::*; +use serde_json::Value; + +/// Reads the value as `type_name`, re-runs its validate step and writes it back out. +/// +/// Every fixture assertion goes through this, so a type that reads a field but forgets to +/// write it back fails the round trip. +pub fn round_trip(type_name: &str, value: &Value) -> std::result::Result { + macro_rules! arm { + ($t:ty) => { + if type_name == <$t as DomainType>::TYPE_NAME { + let parsed = <$t as DomainType>::from_json(value)?; + parsed.validate()?; + return Ok(parsed.to_json()); + } + }; + } + arm!(Scope); + arm!(RationalNs); + arm!(SchemaRef); + arm!(TypedValue); + arm!(SessionRpcRequest); + arm!(SessionRpcSuccess); + arm!(SessionRpcFailure); + arm!(AssetRef); + arm!(SensoryInput); + arm!(Stimulus); + arm!(Reward); + arm!(AgentTelemetry); + arm!(AgentInitializeParams); + arm!(AgentInitializeResult); + arm!(PrepareParams); + arm!(PreparedDecision); + arm!(CommitParams); + arm!(AgentCommitResult); + arm!(ControllerSchema); + arm!(PortControl); + arm!(EnvironmentDescriptor); + arm!(EnvironmentInitializeParams); + arm!(EnvironmentInitializeResult); + arm!(WorldObservation); + arm!(AdvanceParams); + arm!(StepResult); + arm!(HelloParams); + arm!(HelloResult); + arm!(StatusResult); + arm!(AcknowledgeParams); + arm!(AcknowledgeResult); + arm!(ShutdownParams); + arm!(ShutdownResult); + arm!(TaskEvent); + arm!(EpisodeRequest); + arm!(ViewDescriptor); + arm!(ViewRef); + arm!(AudioDescriptor); + arm!(AudioRef); + arm!(CaptureParams); + arm!(CaptureResult); + arm!(StageRestoreParams); + arm!(StageRestoreResult); + arm!(ActivateRestoreParams); + arm!(ActivateRestoreResult); + arm!(SessionDescriptor); + arm!(CommittedSnapshot); + arm!(TraceBehaviour); + arm!(TraceOperational); + arm!(TransitionTrace); + Err(WireError(format!( + "no fixture reader for type {type_name:?}" + ))) +} + +/// The type names `round_trip` knows. +pub const READABLE_TYPES: &[&str] = &[ + "Scope", + "RationalNs", + "SchemaRef", + "TypedValue", + "SessionRpcRequest", + "SessionRpcSuccess", + "SessionRpcFailure", + "AssetRef", + "SensoryInput", + "Stimulus", + "Reward", + "AgentTelemetry", + "AgentInitializeParams", + "AgentInitializeResult", + "PrepareParams", + "PreparedDecision", + "CommitParams", + "AgentCommitResult", + "ControllerSchema", + "PortControl", + "EnvironmentDescriptor", + "EnvironmentInitializeParams", + "EnvironmentInitializeResult", + "WorldObservation", + "AdvanceParams", + "StepResult", + "HelloParams", + "HelloResult", + "StatusResult", + "AcknowledgeParams", + "AcknowledgeResult", + "ShutdownParams", + "ShutdownResult", + "TaskEvent", + "EpisodeRequest", + "ViewDescriptor", + "ViewRef", + "AudioDescriptor", + "AudioRef", + "CaptureParams", + "CaptureResult", + "StageRestoreParams", + "StageRestoreResult", + "ActivateRestoreParams", + "ActivateRestoreResult", + "SessionDescriptor", + "CommittedSnapshot", + "TraceBehaviour", + "TraceOperational", + "TransitionTrace", +]; diff --git a/services/flysim/crates/fly-session-types/tests/descriptor_checks.rs b/services/flysim/crates/fly-session-types/tests/descriptor_checks.rs new file mode 100644 index 0000000..cee4d49 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/descriptor_checks.rs @@ -0,0 +1,78 @@ +//! Rules that need a descriptor in hand: complete batches, the observation delay rule, byte +//! shapes and descriptor agreement. + +use fly_session_types::fixtures; +use fly_session_types::publishing::{CommittedSnapshot, SessionDescriptor}; +use fly_session_types::scalar::{DomainType, Result}; +use fly_session_types::workers::{ + EnvironmentDescriptor, PortControl, SensoryInput, StepResult, WorldObservation, +}; + +#[test] +fn every_descriptor_check_lands_the_way_the_fixture_says() { + let file = fixtures::load("descriptor-checks.json").expect("descriptor-checks.json"); + let descriptor = + EnvironmentDescriptor::from_json(file.get("descriptor").expect("descriptor")).expect("descriptor"); + let delayed = EnvironmentDescriptor::from_json(file.get("delayedDescriptor").expect("delayed")) + .expect("delayed descriptor"); + let session = SessionDescriptor::from_json(file.get("sessionDescriptor").expect("session")) + .expect("session descriptor"); + let previous = WorldObservation::from_json(file.get("stepResultPrevious").expect("previous")) + .expect("previous observation"); + + for case in fixtures::cases(&file).expect("cases") { + let name = fixtures::field(case, "name").expect("name"); + let kind = fixtures::field(case, "kind").expect("kind"); + let reason = fixtures::field(case, "reason").unwrap_or(""); + let expect_accept = fixtures::field(case, "expect").expect("expect") == "accept"; + let value = case.get("value").expect("value"); + let outcome: Result<()> = match kind { + "portControl" => PortControl::from_json(value).and_then(|control| { + match descriptor.port(&control.port_id) { + Some(port) => control.validate_against(&port.controls), + None => fly_session_types::scalar::err("no such port"), + } + }), + "advanceControls" => value + .as_array() + .expect("an array of controls") + .iter() + .map(PortControl::from_json) + .collect::>>() + .and_then(|controls| descriptor.validate_batch(&controls)), + "sensoryInput" => SensoryInput::from_json(value) + .and_then(|input| input.validate_against(&descriptor.views)), + "sensoryInputDelayed" => { + SensoryInput::from_json(value).and_then(|input| input.validate_against(&delayed.views)) + } + "worldObservation" => WorldObservation::from_json(value) + .and_then(|observation| observation.validate_against(&descriptor)), + "stepResult" => StepResult::from_json(value) + .and_then(|result| result.validate_against(&descriptor, &previous)), + "snapshot" => CommittedSnapshot::from_json(value) + .and_then(|snapshot| snapshot.validate_against(&session)), + other => panic!("unknown descriptor check kind {other:?}"), + }; + assert_eq!( + outcome.is_ok(), + expect_accept, + "{name}: expected {}. {reason}. outcome: {outcome:?}", + if expect_accept { "accept" } else { "reject" } + ); + } +} + +/// The delay rule itself, stated once: `max(0, boundary - observationDelaySteps)`. +#[test] +fn the_required_producing_boundary_saturates_at_zero() { + let file = fixtures::load("descriptor-checks.json").expect("descriptor-checks.json"); + let delayed = EnvironmentDescriptor::from_json(file.get("delayedDescriptor").expect("delayed")) + .expect("delayed descriptor"); + let view = &delayed.views[0]; + assert_eq!(view.observation_delay_steps, 2); + assert_eq!(view.required_produced_step(0), 0); + assert_eq!(view.required_produced_step(1), 0); + assert_eq!(view.required_produced_step(2), 0); + assert_eq!(view.required_produced_step(3), 1); + assert_eq!(view.frame_bytes(), u64::from(160u32 * 4 * 144)); +} diff --git a/services/flysim/crates/fly-session-types/tests/encodings.rs b/services/flysim/crates/fly-session-types/tests/encodings.rs new file mode 100644 index 0000000..d2321e7 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/encodings.rs @@ -0,0 +1,169 @@ +//! The scalar encodings agree with the bus's, and the four identities cannot be confused. + +use fly_session_types::fixtures; +use fly_session_types::scalar::{ + ArtifactIdentity, BusCallId, DomainRequestId, OwnerKind, OwnerToken, is_digest, is_id, parse_u64, +}; +use serde_json::Value; + +/// The domain `Id`, `U64` and `Digest` are the bus encodings, not a second opinion about them. +#[test] +fn domain_scalars_are_the_bus_scalars() { + let ids = [ + "a", + "fly-a", + "0", + "a.b_c-d", + "", + "A", + "-a", + ".a", + "a b", + "fly/a", + &"a".repeat(64), + &"a".repeat(65), + ]; + for id in ids { + assert_eq!( + is_id(id), + flybus::wire::is_id(id), + "Id encoding must agree with the bus for {id:?}" + ); + } + let numbers = [ + "0", + "1", + "18446744073709551615", + "18446744073709551616", + "01", + "", + "-1", + "1.0", + " 1", + ]; + for text in numbers { + assert_eq!( + parse_u64(text), + flybus::wire::parse_u64(text), + "U64 encoding must agree with the bus for {text:?}" + ); + } + let digests = [ + &"a".repeat(64), + &"0".repeat(64), + &"A".repeat(64), + &"g".repeat(64), + &"a".repeat(63), + ]; + for digest in digests { + assert_eq!( + is_digest(digest), + flybus::wire::is_digest(digest), + "Digest encoding must agree with the bus" + ); + } +} + +#[test] +fn u64_boundaries_reject_from_the_fixture() { + let file = fixtures::load("boundaries.json").expect("boundaries.json"); + let cases = file.get("u64").and_then(Value::as_array).expect("u64"); + for case in cases { + let text = fixtures::field(case, "text").expect("text"); + let accept = case.get("accept").and_then(Value::as_bool).expect("accept"); + assert_eq!( + parse_u64(text).is_some(), + accept, + "{text:?}: {}", + fixtures::field(case, "reason").unwrap_or("") + ); + } +} + +/// bus callId, domain requestId and delivery/hold owner tokens are four types. The fixture +/// says, for every spelling, which of them accept it: no string is accepted by two. +#[test] +fn the_four_identities_never_accept_each_others_spellings() { + let file = fixtures::load("identities.json").expect("identities.json"); + for case in fixtures::cases(&file).expect("cases") { + let text = fixtures::field(case, "text").expect("text"); + let call = case.get("busCallId").and_then(Value::as_bool).expect("busCallId"); + let request = case + .get("domainRequestId") + .and_then(Value::as_bool) + .expect("domainRequestId"); + let owner = case.get("ownerToken").expect("ownerToken"); + assert_eq!(BusCallId::parse(text).is_ok(), call, "busCallId {text:?}"); + assert_eq!( + DomainRequestId::parse(text).is_ok(), + request, + "domainRequestId {text:?}" + ); + match owner { + Value::Null => assert!( + OwnerToken::parse(text).is_err(), + "owner token {text:?} must be refused" + ), + Value::String(kind) => { + let parsed = OwnerToken::parse(text).expect("an owner token"); + let expected = match kind.as_str() { + "delivery" => OwnerKind::Delivery, + "hold" => OwnerKind::Hold, + other => panic!("unknown owner kind {other:?}"), + }; + assert_eq!(parsed.kind(), expected, "owner kind of {text:?}"); + } + other => panic!("unexpected ownerToken field {other:?}"), + } + let accepted = [call, request, OwnerToken::parse(text).is_ok()] + .iter() + .filter(|a| **a) + .count(); + assert!( + accepted <= 1, + "{text:?} is accepted by more than one identity type" + ); + } +} + +/// An artifact identity is the naming half of a bus ArtifactRef, and nothing else in these +/// contracts is one: an AssetRef is persistent installed content, not live bytes. +#[test] +fn artifact_identity_is_the_naming_half_of_an_artifact_ref() { + let file = fixtures::load("identities.json").expect("identities.json"); + let section = file.get("artifact").expect("artifact"); + let reference = + flybus::wire::ArtifactRef::from_json(section.get("ref").expect("ref")).expect("a ref"); + let identity = ArtifactIdentity::of(&reference); + identity.validate().expect("a valid identity"); + let expected = section.get("identity").expect("identity"); + assert_eq!(identity.store_id, expected["storeId"].as_str().unwrap()); + assert_eq!(identity.artifact_id, expected["artifactId"].as_str().unwrap()); + assert_eq!(identity.generation.to_string(), expected["generation"].as_str().unwrap()); + + let asset = fly_session_types::workers::AssetRef::from_json(section_asset(&file)).expect("asset"); + assert_ne!( + asset.id, identity.artifact_id, + "the fixture's asset and artifact are deliberately different things" + ); +} + +fn section_asset(file: &Value) -> &Value { + file.get("asset").expect("asset") +} + +/// A delivery id or hold token is connection-private. The domain reader has no field that +/// takes one, which is what `canonical::reject_bus_identities` enforces; here we only pin +/// that the two prefixes the bus issues are the two kinds this type knows. +#[test] +fn owner_tokens_come_in_exactly_two_kinds() { + assert_eq!(OwnerToken::delivery(7).as_str(), "dlv-7"); + assert_eq!(OwnerToken::hold(9).as_str(), "own-9"); + assert_eq!(OwnerToken::delivery(7).kind(), OwnerKind::Delivery); + assert_eq!(OwnerToken::hold(9).kind(), OwnerKind::Hold); + assert_eq!(BusCallId::from_serial(12).as_str(), "call-12"); + assert_eq!(DomainRequestId::from_serial(41).as_str(), "req-41"); + assert_eq!(DomainRequestId::from_serial(41).serial(), 41); +} + +use fly_session_types::scalar::DomainType as _; diff --git a/services/flysim/crates/fly-session-types/tests/payloads.rs b/services/flysim/crates/fly-session-types/tests/payloads.rs new file mode 100644 index 0000000..0cdd576 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/payloads.rs @@ -0,0 +1,177 @@ +//! The `valid.json`, `invalid.json`, `raw.json` and `generated.json` fixtures. + +mod common; + +use fly_session_types::scalar::{DomainType, MAX_TYPED_VALUE_BYTES, SchemaRef, TypedValue}; +use fly_session_types::{canonical, fixtures}; +use serde_json::{Value, json}; + +use common::round_trip; + +#[test] +fn every_valid_case_round_trips_and_canonicalizes_to_its_recorded_bytes() { + let file = fixtures::load("valid.json").expect("valid.json"); + let cases = fixtures::cases(&file).expect("cases"); + for case in cases { + let name = fixtures::field(case, "name").expect("name"); + let type_name = fixtures::field(case, "type").expect("type"); + let value = case.get("value").expect("value"); + let written = round_trip(type_name, value) + .unwrap_or_else(|e| panic!("{name} ({type_name}) must be accepted: {e}")); + let canonical_in = canonical::canonicalize(value).expect("canonicalizable"); + let canonical_out = canonical::canonicalize(&written).expect("canonicalizable"); + assert_eq!( + canonical_in, canonical_out, + "{name}: reading and writing must preserve every field" + ); + assert_eq!( + canonical_in, + fixtures::field(case, "canonical").expect("canonical"), + "{name}: canonical JSON must match the fixture" + ); + assert_eq!( + canonical::sha256_hex(canonical_in.as_bytes()), + fixtures::field(case, "digest").expect("digest"), + "{name}: digest must match the fixture" + ); + } + assert!(cases.len() >= 70, "the valid fixture should stay broad"); +} + +#[test] +fn every_type_the_readers_know_appears_in_the_valid_fixture() { + let file = fixtures::load("valid.json").expect("valid.json"); + let cases = fixtures::cases(&file).expect("cases"); + let covered: Vec<&str> = cases + .iter() + .map(|case| fixtures::field(case, "type").expect("type")) + .collect(); + let missing: Vec<&&str> = common::READABLE_TYPES + .iter() + .filter(|t| !covered.contains(*t)) + .collect(); + assert!( + missing.is_empty(), + "every readable type needs at least one accepted fixture; missing {missing:?}" + ); +} + +#[test] +fn every_invalid_case_is_refused() { + let file = fixtures::load("invalid.json").expect("invalid.json"); + let cases = fixtures::cases(&file).expect("cases"); + for case in cases { + let name = fixtures::field(case, "name").expect("name"); + let type_name = fixtures::field(case, "type").expect("type"); + let reason = fixtures::field(case, "reason").expect("reason"); + let value = case.get("value").expect("value"); + let outcome = round_trip(type_name, value); + assert!( + outcome.is_err(), + "{name} ({type_name}) must be refused: {reason}" + ); + } + assert!(cases.len() >= 80, "the invalid fixture should stay broad"); +} + +#[test] +fn every_raw_byte_case_is_refused_before_or_during_validation() { + let file = fixtures::load("raw.json").expect("raw.json"); + for case in fixtures::cases(&file).expect("cases") { + let name = fixtures::field(case, "name").expect("name"); + let type_name = fixtures::field(case, "type").expect("type"); + let reason = fixtures::field(case, "reason").expect("reason"); + let bytes = fixtures::base64(case, "base64").unwrap_or_default(); + let outcome = canonical::parse_strict(&bytes).and_then(|value| { + round_trip(type_name, &value).map_err(|e| fly_session_types::scalar::wire_err(e.0)) + }); + assert!(outcome.is_err(), "{name} must be refused: {reason}"); + } +} + +/// The recipes in `generated.json`: payloads too large to store as fixtures. +#[test] +fn generated_boundary_cases_land_on_the_right_side_of_every_limit() { + let file = fixtures::load("generated.json").expect("generated.json"); + let pad_schema = SchemaRef::from_json(file.get("padSchema").expect("padSchema")).expect("schema"); + for case in fixtures::cases(&file).expect("cases") { + let name = fixtures::field(case, "name").expect("name"); + let kind = fixtures::field(case, "kind").expect("kind"); + let expect_accept = fixtures::field(case, "expect").expect("expect") == "accept"; + let outcome: Result<(), String> = match kind { + "padded-typed-value" => { + let pad = case.get("padCharacters").and_then(Value::as_u64).expect("pad") as usize; + TypedValue::new(pad_schema.clone(), json!({"pad": "a".repeat(pad)})) + .map(|_| ()) + .map_err(|e| e.0) + } + "padded-request" => { + let pad = case.get("padCharacters").and_then(Value::as_u64).expect("pad") as usize; + let total = case + .get("envelopeTotal") + .and_then(Value::as_u64) + .expect("envelopeTotal") as usize; + let request = json!({ + "requestId": "req-1", + "scope": Value::Null, + "params": {"pad": "a".repeat(pad)}, + }); + let body = round_trip("SessionRpcRequest", &request).expect("a request"); + let length = canonical::canonicalize(&body).expect("canonicalizable").len(); + canonical::require_envelope_fit(&body, total - length) + .map(|_| ()) + .map_err(|e| e.0) + } + "error-message" | "error-message-astral" => { + let points = case.get("codePoints").and_then(Value::as_u64).expect("codePoints") + as usize; + let character = if kind == "error-message" { 'x' } else { '\u{10400}' }; + let message: String = std::iter::repeat_n(character, points).collect(); + let failure = json!({ + "type": "error", + "requestId": "req-41", + "workerId": "fly-a", + "incarnationId": "inc-1", + "scope": Value::Null, + "error": {"code": "INTERNAL", "message": message, "mutation": "unknown"}, + }); + round_trip("SessionRpcFailure", &failure) + .map(|_| ()) + .map_err(|e| e.0) + } + other => panic!("unknown generated case kind {other:?}"), + }; + assert_eq!( + outcome.is_ok(), + expect_accept, + "{name}: expected {}, got {outcome:?}", + if expect_accept { "accept" } else { "reject" } + ); + } +} + +#[test] +fn a_typed_value_at_the_cap_is_accepted_and_one_byte_more_is_not() { + let schema = SchemaRef::new("pad.v1", 1, &canonical::sha256_hex(b"pad.v1")).expect("schema"); + let overhead = canonical::canonicalize( + &TypedValue::new(schema.clone(), json!({"pad": ""})) + .expect("empty") + .to_json(), + ) + .expect("canonicalizable") + .len(); + let at_cap = TypedValue::new( + schema.clone(), + json!({"pad": "a".repeat(MAX_TYPED_VALUE_BYTES - overhead)}), + ) + .expect("exactly at the cap"); + assert_eq!(at_cap.canonical_len().expect("length"), MAX_TYPED_VALUE_BYTES); + assert!( + TypedValue::new( + schema, + json!({"pad": "a".repeat(MAX_TYPED_VALUE_BYTES - overhead + 1)}) + ) + .is_err(), + "one byte over the cap must fail" + ); +} diff --git a/services/flysim/crates/fly-session-types/tests/rational.rs b/services/flysim/crates/fly-session-types/tests/rational.rs new file mode 100644 index 0000000..e73341a --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/rational.rs @@ -0,0 +1,140 @@ +//! Checked rational arithmetic and the step-v1 section 5 tick accumulator. + +use fly_session_types::scalar::{DomainType, RationalNs}; +use fly_session_types::fixtures; +use serde_json::Value; + +fn rational(value: &Value) -> RationalNs { + RationalNs::from_json(value).expect("a valid rational") +} + +#[test] +fn the_accumulator_produces_the_fixture_tick_counts_and_remainders() { + let file = fixtures::load("rational.json").expect("rational.json"); + for case in file + .get("accumulator") + .and_then(Value::as_array) + .expect("accumulator") + { + let name = fixtures::field(case, "name").expect("name"); + let step = rational(case.get("stepDuration").expect("stepDuration")); + let tick = rational(case.get("tickDuration").expect("tickDuration")); + let mut accumulator = RationalNs::ZERO; + let mut total = 0u64; + for (index, expected) in case + .get("steps") + .and_then(Value::as_array) + .expect("steps") + .iter() + .enumerate() + { + accumulator = accumulator.checked_add(&step).expect("checked add"); + let (ticks, remainder) = accumulator.divide_floor(&tick).expect("checked divide"); + accumulator = remainder; + total += ticks; + assert_eq!( + ticks.to_string(), + fixtures::field(expected, "ticks").expect("ticks"), + "{name}: tick count at step {index}" + ); + assert_eq!( + remainder, + rational(expected.get("remainder").expect("remainder")), + "{name}: remainder at step {index}" + ); + assert!( + remainder < tick, + "{name}: the remainder is always less than one model tick" + ); + } + assert_eq!( + total.to_string(), + fixtures::field(case, "totalTicks").expect("totalTicks"), + "{name}: total ticks" + ); + } +} + +#[test] +fn checked_arithmetic_reduces_or_refuses() { + let file = fixtures::load("rational.json").expect("rational.json"); + for case in file.get("add").and_then(Value::as_array).expect("add") { + let outcome = rational(case.get("a").expect("a")).checked_add(&rational(case.get("b").expect("b"))); + match case.get("sum") { + Some(sum) => assert_eq!(outcome.expect("a sum"), rational(sum)), + None => assert!(outcome.is_err(), "the sum must overflow: {case}"), + } + } + for case in file + .get("subtract") + .and_then(Value::as_array) + .expect("subtract") + { + let outcome = + rational(case.get("a").expect("a")).checked_sub(&rational(case.get("b").expect("b"))); + match case.get("difference") { + Some(difference) => assert_eq!(outcome.expect("a difference"), rational(difference)), + None => assert!(outcome.is_err(), "the subtraction must fail: {case}"), + } + } + for case in file + .get("multiply") + .and_then(Value::as_array) + .expect("multiply") + { + let k: u64 = fixtures::field(case, "k") + .expect("k") + .parse() + .expect("a u64"); + let outcome = rational(case.get("a").expect("a")).checked_mul_u64(k); + match case.get("product") { + Some(product) => assert_eq!(outcome.expect("a product"), rational(product)), + None => assert!(outcome.is_err(), "the product must overflow: {case}"), + } + } + for case in file + .get("compare") + .and_then(Value::as_array) + .expect("compare") + { + let left = rational(case.get("a").expect("a")); + let right = rational(case.get("b").expect("b")); + let ordering = match fixtures::field(case, "ordering").expect("ordering") { + "less" => std::cmp::Ordering::Less, + "equal" => std::cmp::Ordering::Equal, + "greater" => std::cmp::Ordering::Greater, + other => panic!("unknown ordering {other:?}"), + }; + assert_eq!(left.cmp(&right), ordering, "{case}"); + } +} + +#[test] +fn zero_has_exactly_one_encoding_and_durations_must_be_positive() { + assert_eq!(RationalNs::ZERO, RationalNs::new(0, 1).expect("0/1")); + assert!(RationalNs::new(0, 2).is_err(), "zero is encoded 0/1"); + assert!(RationalNs::new(1, 0).is_err(), "denominators are positive"); + assert!(RationalNs::new(2, 4).is_err(), "fractions are reduced"); + assert!(RationalNs::ZERO.require_positive("worldTime").is_err()); + assert!( + RationalNs::new(1, 3) + .expect("1/3") + .require_positive("tickDuration") + .is_ok() + ); + assert!( + RationalNs::ZERO.divide_floor(&RationalNs::ZERO).is_err(), + "dividing by a zero tick is refused, not infinite" + ); +} + +#[test] +fn reduction_refuses_a_result_that_does_not_fit_u64() { + let big = RationalNs::new(u64::MAX, 1).expect("a whole number"); + assert!(big.checked_mul_u64(2).is_err()); + assert!(big.checked_add(&big).is_err()); + assert_eq!( + RationalNs::reduced(u128::from(u64::MAX) * 2, 2).expect("reduces back into range"), + big + ); +} diff --git a/services/flysim/crates/fly-session-types/tests/schema_set.rs b/services/flysim/crates/fly-session-types/tests/schema_set.rs new file mode 100644 index 0000000..5943def --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/schema_set.rs @@ -0,0 +1,187 @@ +//! The canonical schema set, `contractDigest` and the freshness of every derived fixture. + +use fly_session_types::{canonical, fixtures, schema}; +use serde_json::Value; + +#[path = "../examples/update_fixtures.rs"] +#[allow(dead_code, reason = "the example's main is not used by the test that reuses its writers")] +mod updater; + +/// Every derived fixture is exactly what the updater writes today. If this fails, run +/// `cargo run -p fly-session-types --example update_fixtures` and review the diff. +#[test] +fn derived_fixtures_are_current() { + for (name, expected) in updater::derived() { + let path = fixtures::dir().join(&name); + let found = std::fs::read_to_string(&path).expect("a checked-in fixture"); + assert_eq!( + found, expected, + "{name} is stale; regenerate it with the update_fixtures example" + ); + } +} + +#[test] +fn the_contract_digest_is_the_digest_of_the_checked_in_schema_set() { + let text = fixtures::load_bytes("schema-set.json").expect("schema-set.json"); + let recorded = fixtures::load("contract-digest.json").expect("contract-digest.json"); + let expected = recorded + .get("contractDigest") + .and_then(Value::as_str) + .expect("contractDigest"); + assert_eq!(schema::contract_digest(), expected); + // The file is the canonical schema set plus one trailing newline. + assert_eq!( + canonical::sha256_hex(text.strip_suffix(b"\n").expect("trailing newline")), + expected, + "the digest is over the canonical schema set, byte for byte" + ); +} + +/// The digest comes from the schema declaration, not from the source text: reparsing the +/// checked-in file with different whitespace and key order gives the same digest. +#[test] +fn the_contract_digest_survives_reformatting() { + let bytes = fixtures::load_bytes("schema-set.json").expect("schema-set.json"); + let parsed = canonical::parse_strict(bytes.strip_suffix(b"\n").expect("newline")).expect("parses"); + let pretty = serde_json::to_vec_pretty(&parsed).expect("serializable"); + let reparsed = canonical::parse_strict(&pretty).expect("parses"); + assert_eq!( + canonical::digest_of(&reparsed).expect("digest"), + schema::contract_digest(), + "pretty printing the schema set does not change its digest" + ); + let shuffled = canonical::parse_strict( + br#"{"version":1,"contract":"fly-session-types-other"}"#, + ) + .expect("parses"); + assert_ne!( + canonical::digest_of(&shuffled).expect("digest"), + schema::contract_digest() + ); +} + +/// ... and it changes when a schema changes: a renamed field, a widened bound, one more enum +/// member or one fewer type all move the digest. +#[test] +fn the_contract_digest_changes_when_a_schema_changes() { + let baseline = schema::contract_digest(); + let mutate = |mutation: fn(&mut Value)| { + let mut set = schema::schema_set(); + mutation(&mut set); + canonical::digest_of(&set).expect("digest") + }; + let renamed_field = mutate(|set| { + set["types"][0]["fields"][0]["name"] = Value::String("sessionIdentifier".to_owned()); + }); + let widened_bound = mutate(|set| { + for limit in set["limits"].as_array_mut().expect("limits") { + if limit["name"] == Value::String("maxAgents".to_owned()) { + limit["value"] = Value::from(8u64); + } + } + }); + let extra_enum_member = mutate(|set| { + set["enums"][0]["members"] + .as_array_mut() + .expect("members") + .push(Value::String("s16le-interleaved".to_owned())); + }); + let dropped_type = mutate(|set| { + set["types"].as_array_mut().expect("types").pop(); + }); + let relaxed_constraint = mutate(|set| { + set["types"][0]["fields"][0]["constraint"] = Value::String("anything".to_owned()); + }); + for (what, digest) in [ + ("a renamed field", renamed_field), + ("a widened bound", widened_bound), + ("an extra enum member", extra_enum_member), + ("a dropped type", dropped_type), + ("a relaxed constraint", relaxed_constraint), + ] { + assert_ne!(digest, baseline, "{what} must change contractDigest"); + } +} + +#[test] +fn the_schema_set_names_every_type_the_crate_reads() { + let set = schema::schema_set(); + let names: Vec<&str> = set["types"] + .as_array() + .expect("types") + .iter() + .map(|t| t["name"].as_str().expect("name")) + .collect(); + for expected in [ + "Scope", + "RationalNs", + "TypedValue", + "SessionRpcRequest", + "SessionRpcFailure", + "PrepareParams", + "StepResult", + "ViewRef", + "AudioRef", + "CaptureResult", + "SessionDescriptor", + "CommittedSnapshot", + "TraceBehaviour", + "TraceOperational", + ] { + assert!(names.contains(&expected), "the schema set must name {expected}"); + } + let mut sorted = names.clone(); + sorted.sort_unstable(); + assert_eq!(names, sorted, "the rendered set is sorted by type name"); + let mut unique = sorted.clone(); + unique.dedup(); + assert_eq!(unique.len(), names.len(), "no type is declared twice"); +} + +/// Every bound the schema set publishes is the constant the code enforces, and every bound +/// this crate chose rather than read from a document says so. +#[test] +fn published_limits_match_the_constants_and_name_their_source() { + let set = schema::schema_set(); + let limits = set["limits"].as_array().expect("limits"); + let find = |name: &str| -> u64 { + limits + .iter() + .find(|l| l["name"] == Value::String(name.to_owned())) + .and_then(|l| l["value"].as_u64()) + .unwrap_or_else(|| panic!("the schema set must publish {name}")) + }; + assert_eq!(find("maxAgents"), fly_session_types::workers::MAX_AGENTS as u64); + assert_eq!(find("maxPorts"), fly_session_types::workers::MAX_PORTS as u64); + assert_eq!( + find("maxRateRoles"), + fly_session_types::workers::MAX_RATE_ROLES as u64 + ); + assert_eq!(find("maxViews"), fly_session_types::media::MAX_VIEWS as u64); + assert_eq!( + find("maxTypedValueBytes"), + fly_session_types::scalar::MAX_TYPED_VALUE_BYTES as u64 + ); + assert_eq!( + find("maxEnvelopeBytes"), + canonical::MAX_ENVELOPE_BYTES as u64 + ); + let crate_chosen: Vec<&str> = limits + .iter() + .filter(|l| l["source"] == Value::String("crate".to_owned())) + .map(|l| l["name"].as_str().expect("name")) + .collect(); + assert_eq!( + crate_chosen, + [ + "maxAssets", + "maxAudioStreams", + "maxCapabilities", + "maxSnapshotEvents", + "maxSupportedMajors", + "maxSupportedStimuli", + ], + "a bound with no stated source must be declared as this crate's choice" + ); +} diff --git a/services/flysim/crates/fly-session-types/tests/seeds.rs b/services/flysim/crates/fly-session-types/tests/seeds.rs new file mode 100644 index 0000000..a5f32a0 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/seeds.rs @@ -0,0 +1,120 @@ +//! `seed-derivation-v1` against its test vectors. + +use fly_session_types::{fixtures, seed}; +use serde_json::Value; + +#[test] +fn every_vector_derives_its_recorded_seed() { + let file = fixtures::load("seed-vectors.json").expect("seed-vectors.json"); + assert_eq!( + file.get("algorithm").and_then(Value::as_str), + Some(seed::ALGORITHM) + ); + let vectors = file.get("vectors").and_then(Value::as_array).expect("vectors"); + for case in vectors { + let master: u64 = fixtures::field(case, "masterSeed") + .expect("masterSeed") + .parse() + .expect("a U64"); + let agent = fixtures::field(case, "agentId").expect("agentId"); + assert_eq!( + String::from_utf8(seed::material(master, agent).expect("material")).expect("utf-8"), + fixtures::field(case, "material").expect("material"), + "the hashed material is part of the specification" + ); + assert_eq!( + seed::material_digest(master, agent).expect("digest"), + fixtures::field(case, "materialDigest").expect("materialDigest") + ); + assert_eq!( + i64::from(seed::agent_seed(master, agent).expect("seed")), + case.get("seed").and_then(Value::as_i64).expect("seed"), + "seed for {agent} under master {master}" + ); + } + assert!(vectors.len() >= 20, "keep the vector table broad"); +} + +#[test] +fn one_composition_gets_independent_seeds() { + let file = fixtures::load("seed-vectors.json").expect("seed-vectors.json"); + let composition = file.get("composition").expect("composition"); + let master: u64 = fixtures::field(composition, "masterSeed") + .expect("masterSeed") + .parse() + .expect("a U64"); + let ids: Vec = composition + .get("agentIds") + .and_then(Value::as_array) + .expect("agentIds") + .iter() + .map(|v| v.as_str().expect("an id").to_owned()) + .collect(); + let seeds = seed::composition_seeds(master, &ids).expect("seeds"); + let recorded: Vec = composition + .get("seeds") + .and_then(Value::as_array) + .expect("seeds") + .iter() + .map(|v| v.as_i64().expect("a seed")) + .collect(); + assert_eq!( + seeds.iter().map(|s| i64::from(*s)).collect::>(), + recorded + ); + let mut unique = seeds.clone(); + unique.sort_unstable(); + unique.dedup(); + assert_eq!(unique.len(), seeds.len(), "per-agent seeds are independent"); + assert!( + seeds.iter().all(|s| *s != 0), + "a zero seed would stall an xorshift generator" + ); +} + +#[test] +fn a_different_master_seed_or_agent_id_derives_a_different_seed() { + assert_ne!( + seed::agent_seed(0, "fly-a").expect("seed"), + seed::agent_seed(1, "fly-a").expect("seed") + ); + assert_ne!( + seed::agent_seed(0, "fly-a").expect("seed"), + seed::agent_seed(0, "fly-b").expect("seed") + ); + assert_eq!( + seed::agent_seed(7, "fly-a").expect("seed"), + seed::agent_seed(7, "fly-a").expect("seed"), + "the derivation is a function of its recorded inputs" + ); +} + +#[test] +fn invalid_inputs_are_refused_rather_than_normalized() { + let file = fixtures::load("seed-vectors.json").expect("seed-vectors.json"); + for case in file.get("invalid").and_then(Value::as_array).expect("invalid") { + let master: u64 = fixtures::field(case, "masterSeed") + .expect("masterSeed") + .parse() + .expect("a U64"); + if let Ok(agent) = fixtures::field(case, "agentId") { + assert!( + seed::agent_seed(master, agent).is_err(), + "{agent:?} must be refused: {}", + fixtures::field(case, "reason").unwrap_or("") + ); + } else { + let ids: Vec = case + .get("agentIds") + .and_then(Value::as_array) + .expect("agentIds") + .iter() + .map(|v| v.as_str().expect("an id").to_owned()) + .collect(); + assert!( + seed::composition_seeds(master, &ids).is_err(), + "a repeated agent id must be refused" + ); + } + } +} diff --git a/services/flysim/crates/fly-session-types/tests/traces.rs b/services/flysim/crates/fly-session-types/tests/traces.rs new file mode 100644 index 0000000..75103f6 --- /dev/null +++ b/services/flysim/crates/fly-session-types/tests/traces.rs @@ -0,0 +1,113 @@ +//! The step-v1 section 8 trace comparator: behaviour only. + +use fly_session_types::fixtures; +use fly_session_types::scalar::DomainType; +use fly_session_types::trace::TransitionTrace; +use serde_json::Value; + +fn trace(value: &Value) -> TransitionTrace { + TransitionTrace::from_json(value).expect("a valid trace") +} + +#[test] +fn every_variant_compares_the_way_the_fixture_says() { + let file = fixtures::load("traces.json").expect("traces.json"); + let baseline = trace(file.get("baseline").expect("baseline")); + for case in file + .get("variants") + .and_then(Value::as_array) + .expect("variants") + { + let name = fixtures::field(case, "name").expect("name"); + let variant = trace(case.get("trace").expect("trace")); + let expected = case + .get("behaviourEquals") + .and_then(Value::as_bool) + .expect("behaviourEquals"); + let equal = baseline.behaviour_equals(&variant); + let diff = baseline.behaviour_diff(&variant); + assert_eq!( + equal, expected, + "{name}: behaviour equality. differences: {diff:?}" + ); + assert_eq!( + diff.is_empty(), + expected, + "{name}: the diff must be empty exactly when the behaviour matches" + ); + if let Ok(needle) = fixtures::field(case, "diffContains") { + assert!( + diff.iter().any(|line| line.contains(needle)), + "{name}: the diff should name {needle:?}, got {diff:?}" + ); + } + if expected { + assert_eq!( + baseline.behaviour.digest().expect("digest"), + variant.behaviour.digest().expect("digest"), + "{name}: equal behaviour has one digest" + ); + } + } +} + +#[test] +fn a_whole_run_compares_transition_by_transition() { + let file = fixtures::load("traces.json").expect("traces.json"); + let baseline = trace(file.get("baseline").expect("baseline")); + let variants = file + .get("variants") + .and_then(Value::as_array) + .expect("variants"); + let reversed = trace(variants[0].get("trace").expect("trace")); + let changed = trace( + variants + .iter() + .find(|case| fixtures::field(case, "name").unwrap_or("") == "one extra neural tick") + .expect("the extra tick variant") + .get("trace") + .expect("trace"), + ); + assert!(TransitionTrace::runs_equal( + &[baseline.clone(), baseline.clone()], + &[reversed, baseline.clone()] + )); + assert!(!TransitionTrace::runs_equal( + std::slice::from_ref(&baseline), + &[changed] + )); + let longer = [baseline.clone(), baseline.clone()]; + assert!( + !TransitionTrace::runs_equal(std::slice::from_ref(&baseline), &longer), + "a run with more transitions is not the same run" + ); +} + +/// The operational half is recorded, and never part of the comparison. +#[test] +fn operational_metadata_is_recorded_and_excluded() { + let file = fixtures::load("traces.json").expect("traces.json"); + let baseline = trace(file.get("baseline").expect("baseline")); + assert_eq!(baseline.operational.bus_call_ids.len(), 3); + assert_eq!(baseline.operational.prepare_request_ids.len(), 2); + assert_eq!(baseline.operational.delivery_ids.len(), 2); + assert!(baseline.operational.wall_time_ns > 0); + let retried = trace( + file.get("variants") + .and_then(Value::as_array) + .expect("variants") + .iter() + .find(|case| { + fixtures::field(case, "name").unwrap_or("") + == "a safe retry with fresh bus callIds, delivery ids and wall time" + }) + .expect("the retry variant") + .get("trace") + .expect("trace"), + ); + assert_ne!( + baseline.operational, retried.operational, + "the retry really did change the operational half" + ); + assert!(baseline.behaviour_equals(&retried)); +}