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)); +}