From ced832234c9aee3dad44c93d27cd916a58da40b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 17:10:41 -0300 Subject: [PATCH 01/37] ir: extract the validation constraints WA Web enforces, not just field shapes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IR described what fields a stanza has. That is enough to read a well-formed stanza and not enough to produce one, or to know why a real client rejected the one you produced. Every rule below is symmetric — it serves a parser and an emitter equally — and each one has cost a consumer real debugging time. Additive and schema-versioned (schemaVersion 1.0.0 -> 1.1.0): every new field is optional, no existing field changes shape or value, and the whole generated/ diff is verified strictly additive (nothing removed anywhere, including assertion arrays). 1. Echo rules. A new `reference` assertion kind carrying `referencePath`, the argument list of WA's `attrStringFromReference`: an answer's `from` must equal the REQUEST's `to`, its `id` the request's `id`. 17 of the 26 namespace error mixins enforce it, and so does every success parser. An emitter that hardcodes from="s.whatsapp.net" makes every answer to a g.us request unparseable. 579 recovered — including the shapes in the incoming domain, which share the analyzer. 2. Pinned field values. `literalValue` on ParsedField, from both `literal` (required, a hard discriminator) and `optionalLiteral` (pinned only when present) — type="admin" on a successful promote, matched="true"/"false" on a blocklist update, code=429 on a rate-limit error. `required` tells the two apart, which is what decides whether an emitter MUST send the attribute or merely must not contradict it. An optional echo, which guards nothing, rides on the field as `referencePath` instead. Numeric literals are now captured too (they were silently dropped). 2174 pins. 3. Per-RPC error vocabulary. `errorClass` (client/server, derived from the codes, not from the variant's inconsistently-spelled name) plus the closed `errorCodes`/`errorTexts` sets and the `errorCodeMin`/`Max` range of the open-ended fallback arms. The vocabulary is per-RPC: BatchGetGroupInfo takes 400/429 and rejects 404 item-not-found, though that mixin exists and other RPCs use it. `kind` is deliberately left alone so existing consumers keep matching on success/error. 4. Response enums. `enumRef` on ParsedField, resolved through the same path the request side already uses, so an `attrStringEnum` field carries its legal values instead of a bare "type": "enum". 277 resolved. 5. Notification action unions. A new `actions` list per notification: the payload inside the envelope, keyed by child tag. The wireTag -> actionType mapping is many-to-one (not_ephemeral normalises into ephemeral with duration 0, so a NOT_EPHEMERAL branch is dead code) and field names are rebound (the timer arrives in `expiration`, the action field is `duration`). Neither is derivable from the wire. 55 arms, 45 of them w:gp2, with fields, constants, and repeated sub-elements resolved through the handler's local helpers. Guards: - A round-trip test (crates/wa-scan/tests/iq_roundtrip.rs) builds a stanza for every IQ success shape from the IR alone and checks the recorded constraints accept it — 141 shapes, plus a negative control proving a hardcoded `from` is caught. Emitter and checker share no code with the extractor, so a mis-extraction cannot cancel itself out. - manifest.diagnostics.iq.constraints and diagnostics.notif.actions are floor-guarded, so a WA refactor that hides one of these constructs fails the update instead of silently emptying a field. - Constraints seen but not structurally resolvable are counted under dropsByReason, so "no constraint here" and "we failed to extract one" never look alike. Determinism holds (update --check is clean from the pinned bundles) and the reference Rust codegen is byte-identical, verified by A/B-ing it against the same IR with the new fields stripped. --- Cargo.lock | 3 + README.md | 14 +- crates/wa-codegen/src/notif_export.rs | 3 + crates/wa-codegen/src/spec.rs | 14 +- crates/wa-ir/src/iq.rs | 116 + crates/wa-ir/src/lib.rs | 2 +- crates/wa-ir/src/notif.rs | 117 + crates/wa-notif/Cargo.toml | 2 + crates/wa-notif/src/actions.rs | 756 + crates/wa-notif/src/lib.rs | 23 + crates/wa-notif/src/tests.rs | 175 + crates/wa-scan/Cargo.toml | 1 + crates/wa-scan/src/lib.rs | 47 +- crates/wa-scan/src/response.rs | 3 + crates/wa-scan/src/response_index.rs | 270 +- crates/wa-scan/src/response_smax.rs | 541 +- crates/wa-scan/tests/iq_roundtrip.rs | 278 + crates/whatspec/src/main.rs | 134 +- generated/abprops/index.json | 2 +- generated/appstate/index.json | 2 +- generated/enums/index.json | 2 +- generated/incoming/index.json | 388 +- generated/iq/index.json | 21075 +++++++++++++++++++++--- generated/manifest.json | 37 +- generated/mex/index.json | 2 +- generated/notif/index.json | 1072 +- generated/schema/incoming.schema.json | 142 + generated/schema/iq.schema.json | 100 + generated/schema/notif.schema.json | 278 + generated/schema/srvreq.schema.json | 142 + generated/schema/stanza.schema.json | 100 + generated/srvreq/index.json | 1846 ++- generated/stanza/index.json | 2 +- generated/tokens/index.json | 2 +- generated/wam/index.json | 2 +- generated/wasm/index.json | 2 +- 36 files changed, 25349 insertions(+), 2346 deletions(-) create mode 100644 crates/wa-notif/src/actions.rs create mode 100644 crates/wa-scan/tests/iq_roundtrip.rs diff --git a/Cargo.lock b/Cargo.lock index 0351c77..8c06136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1929,6 +1929,8 @@ dependencies = [ "oxc_allocator", "oxc_ast", "oxc_ast_visit", + "oxc_span", + "oxc_syntax", "serde_json", "wa-ir", "wa-oxc", @@ -1971,6 +1973,7 @@ dependencies = [ "oxc_parser", "oxc_span", "oxc_syntax", + "serde_json", "wa-enums", "wa-ir", "wa-oxc", diff --git a/README.md b/README.md index fef1029..7a1acd8 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,24 @@ WhatsApp Web ships its whole protocol (IQ stanzas, protobuf schemas, GraphQL ope | **appstate** | `appstate/index.json` (+ Rust) | app-state (syncd) action schemas + indexing | | **abprops** | `abprops/index.json` (+ Rust) | the A/B-props feature-flag registry (~1.7k flags: code, type, default) | | **enums** | `enums/index.json` (+ Rust) | the wire-enum catalog (nack codes, chat/receipt types, …) | -| **notif** | `notif/index.json` (+ Rust) | the incoming stanza-dispatch catalog: `` kinds + handlers + typed content shapes | +| **notif** | `notif/index.json` (+ Rust) | the incoming stanza-dispatch catalog: `` kinds + handlers + typed content shapes + the payload **action unions** (`w:gp2`'s 40+ group actions) | | **tokens** | `tokens/index.json` (+ `tokens.json`) | the binary-protocol token dictionaries (single-byte + 4 double-byte), wire-indexable | | **wasm** | `wasm/index.json` | the WebAssembly surface: emscripten payload names (`liboqs_wasm_wrapper.wasm`, …) + the bootloader (`bx`) handles the JS resolves their bytes through, with consuming modules | Every domain ships a JSON Schema under `generated/schema/`, and a top-level `generated/manifest.json` stamps the WhatsApp version, per-domain counts, content hashes, and extraction diagnostics. +### Validation constraints, not just field shapes + +Field shapes tell you how to *read* a stanza. They are not enough to *produce* one, nor to explain why a real client rejected the one you produced. So the IR also carries the rules WA Web's own parsers enforce — symmetric by construction, so they serve a parser and an emitter equally: + +- **Echo rules** (`assertions[].kind == "reference"`, and `referencePath` on a field) — an answer's `from` must equal the **request's** `to`, its `id` the request's `id`. `referencePath` is the argument list of WA's `attrStringFromReference`, so `["to"]` means "the request's `to`" and `["account","action"]` means "the `action` attribute of the request's `` child" — no name-matching required. An emitter that hardcodes `from="s.whatsapp.net"` makes every answer to a `g.us` request unparseable. +- **Pinned values** (`literalValue` on a field) — `type="admin"` on a successful promote, `matched="true"`/`"false"` on a blocklist update, `code=429` on a rate-limit error. `required` separates the two forms: a required pin is a hard discriminator (must be present and exact); an optional one is pinned only when present (may be omitted, must never be contradicted). +- **The per-RPC error vocabulary** (`errorClass`, `errorCodes`, `errorTexts`, `errorCodeMin`/`Max` on a response variant) — a **closed** set, and it differs per RPC: `BatchGetGroupInfo` accepts `400 bad-request` and `429 rate-overlimit` and **rejects `404 item-not-found`**, even though that mixin exists and other RPCs use it. `errorCodeMin`/`Max` cover the open-ended fallback arms (any text, code in 400–499 / 500–599). +- **Response enums** (`enumRef` on a field) — the legal values behind an `attrStringEnum`, resolved the same way the request side already resolves them, instead of a bare `"type": "enum"`. +- **Notification action unions** (`notifications[].actions`) — the payload inside the envelope. The `wireTag → actionType` mapping is **many-to-one** (`not_ephemeral` normalises into `ephemeral` with `duration: 0`, so branching on `not_ephemeral` is dead code) and field names are rebound (the disappearing-message timer arrives in `expiration`, but the action field is `duration`). Neither is derivable from the wire. + +Anything the extractor sees but cannot resolve structurally is counted under `manifest.diagnostics.iq.dropsByReason` rather than omitted, so "no constraint here" and "a constraint we failed to extract" never look alike. `manifest.diagnostics.iq.constraints` and `diagnostics.notif.actions` are floor-guarded: a WA refactor that hides one of these constructs fails the update instead of silently emptying a field. + ## Quick start ```sh diff --git a/crates/wa-codegen/src/notif_export.rs b/crates/wa-codegen/src/notif_export.rs index dbeec1e..9bc64b3 100644 --- a/crates/wa-codegen/src/notif_export.rs +++ b/crates/wa-codegen/src/notif_export.rs @@ -295,6 +295,7 @@ mod tests { kind: AssertionKind::Tag, name: Some("notification".into()), value: None, + reference_path: None, }], fields: vec![ParsedField { method: "attrString".into(), @@ -305,6 +306,7 @@ mod tests { }], ..Default::default() }), + actions: Vec::new(), }, NotificationDef { notif_type: "encrypt".into(), @@ -319,6 +321,7 @@ mod tests { }], }], content: None, + actions: Vec::new(), }, ], } diff --git a/crates/wa-codegen/src/spec.rs b/crates/wa-codegen/src/spec.rs index 47eda68..514f031 100644 --- a/crates/wa-codegen/src/spec.rs +++ b/crates/wa-codegen/src/spec.rs @@ -339,7 +339,10 @@ fn emit_success_guards(op: &IqStanzaDef, indent: &str) -> Vec { } } // Tag (the `` root) / FromServer are not success-vs-error discriminators. - AssertionKind::Tag | AssertionKind::FromServer => {} + // Neither is a Reference echo (`from` == the request's `to`): every outcome + // of the same request satisfies it identically, so it separates nothing — + // it is a request-correlation rule, enforced by whoever holds the request. + AssertionKind::Tag | AssertionKind::FromServer | AssertionKind::Reference => {} } } lines @@ -836,8 +839,10 @@ mod tests { kind: AssertionKind::Attr, name: Some("type".into()), value: Some("result".into()), + reference_path: None, }], fields: vec![], + ..Default::default() }]; let guards = emit_success_guards(&s, " "); assert_eq!(guards.len(), 1); @@ -874,6 +879,7 @@ mod tests { kind: ResponseVariantKind::Success, assertions: vec![], fields: vec![attr("token")], + ..Default::default() }, ResponseVariant { tag: "GetThingResponseError".into(), @@ -881,6 +887,7 @@ mod tests { kind: ResponseVariantKind::Error, assertions: vec![], fields: vec![attr("code")], + ..Default::default() }, ], ..Default::default() @@ -930,6 +937,7 @@ mod tests { kind: AssertionKind::Attr, name: Some("type".into()), value: Some(value.into()), + reference_path: None, } } // Success and error read the same field (`type`) — a bare subset that WOULD be @@ -945,6 +953,7 @@ mod tests { kind: ResponseVariantKind::Success, assertions: vec![type_assert("result")], fields: vec![attr("type")], + ..Default::default() }, ResponseVariant { tag: "GetThingResponseError".into(), @@ -952,6 +961,7 @@ mod tests { kind: ResponseVariantKind::Error, assertions: vec![type_assert("error")], fields: vec![attr("type")], + ..Default::default() }, ], ..Default::default() @@ -1000,6 +1010,7 @@ mod tests { kind: ResponseVariantKind::Success, assertions: vec![], fields: vec![attr("type")], + ..Default::default() }, ResponseVariant { tag: "GetThingResponseError".into(), @@ -1007,6 +1018,7 @@ mod tests { kind: ResponseVariantKind::Error, assertions: vec![], fields: vec![attr("type")], + ..Default::default() }, ], ..Default::default() diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index f90553f..85a5d8d 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -255,6 +255,17 @@ pub enum AssertionKind { /// node, "admin_add")`) — a discriminator for marker union variants. The value is /// in [`ResponseAssertion::value`]; `name` is unused. Content, + /// The attribute named by [`ResponseAssertion::name`] must **echo a value taken + /// from the request** — `literal(attrString, node, "from", + /// attrStringFromReference(request, ["to"]))`. The expected value is not a + /// constant, so [`ResponseAssertion::value`] is absent; where it comes from is in + /// [`ResponseAssertion::reference_path`]. + /// + /// This is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success + /// parser) enforce: an answer's `id` must equal the request's `id` and its `from` + /// the request's `to`. An emitter that hardcodes `from="s.whatsapp.net"` produces + /// stanzas a real client cannot parse whenever the request went to `g.us`. + Reference, } /// A single guard a response parser applies. @@ -267,6 +278,17 @@ pub struct ResponseAssertion { pub name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub value: Option, + /// Where the expected value is read from **in the request**, for + /// [`AssertionKind::Reference`]. It is the argument list of WA's + /// `attrStringFromReference(request, path)` helper: every element but the last is a + /// child tag to descend into, and the last is the attribute name. So `["to"]` means + /// "the request's `to` attribute" and `["account", "action"]` means "the `action` + /// attribute of the request's `` child". + /// + /// Carried structurally (rather than left to be inferred from `name`) so a consumer + /// never has to pattern-match attribute names to know an echo rule applies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reference_path: Option>, } /// Scalar type a parsed response field decodes to. @@ -423,6 +445,45 @@ pub struct ParsedField { pub int_max: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub enum_keys: Option>, + /// The wire enum this field's value is drawn from, when the parser reads it with an + /// enum accessor (`attrStringEnum(node, "state", o("Mod").ENUM_OFF_ON)`). Resolved + /// the same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer + /// can type the field as that enum instead of a bare string — and an emitter knows + /// which values are legal instead of guessing from the generated `ENUM_OFF_ON` name. + /// Absent when the enum argument is not a structurally resolvable module export, or + /// resolves to a non-string enum (never guessed). + /// + /// [`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enum_ref: Option, + /// The fixed value the parser pins this field to — `literal(attrString, participant, + /// "type", "admin")` → `"admin"`, `literal(attrInt, error, "code", 429)` → `"429"`. + /// Always the string form of the literal; [`field_type`] says how to read it (a + /// `code` field with `literalValue: "429"` is the integer 429 on the wire). + /// + /// [`required`] tells the two pinning forms apart, and the difference matters to an + /// emitter: a **required** literal (`literal`) is a hard discriminator — the + /// attribute MUST be present and equal to this value or the variant does not match; + /// an **optional** one (`optionalLiteral`) is pinned only when present — the emitter + /// may omit the attribute, but must never send a contradicting value. + /// + /// [`field_type`]: ParsedField::field_type + /// [`required`]: ParsedField::required + #[serde(default, skip_serializing_if = "Option::is_none")] + pub literal_value: Option, + /// The field is pinned not to a constant but to **a value taken from the request** — + /// the same echo rule [`AssertionKind::Reference`] carries, in the same + /// `attrStringFromReference` path form, but for a pin that is *not* a variant guard. + /// + /// A required echo (`literal(…, "from", ref.to)`) is recorded as an assertion, since + /// it must hold for the variant to match at all. An **optional** one + /// (`optionalLiteral(…, "c_dhash", ref.item.dhash)`) is not a guard — the attribute + /// may be absent — so it lives here instead: an emitter may omit it, but if it sends + /// it, the value must be the request's. Mutually exclusive with [`literal_value`]. + /// + /// [`literal_value`]: ParsedField::literal_value + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reference_path: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -456,6 +517,25 @@ pub enum ResponseVariantKind { Alternative, } +/// Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is +/// [`Error`]. WA models the two as separate parsers with disjoint code ranges: a client +/// error carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499) +/// and a server error a 5xx one (`internal-server-error` 500, fallback 500–599). +/// +/// Emitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that +/// only understands `success`/`error` keeps working. +/// +/// [`Error`]: ResponseVariantKind::Error +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "snake_case")] +pub enum ErrorClass { + /// The request was wrong (4xx) — retrying it unchanged will fail again. + Client, + /// The server failed (5xx) — the same request may succeed later. + Server, +} + /// One alternative of a response-root discriminated union (an `WASmaxInResponse` /// variant aggregated by the `WASmaxRPC` orchestrator). #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] @@ -467,6 +547,42 @@ pub struct ResponseVariant { pub tag: String, pub module_name: String, pub kind: ResponseVariantKind, + /// For an error variant, whether it is the client-error (4xx) or server-error (5xx) + /// arm. Absent for a success variant, and for an error variant whose parser carries + /// no recoverable code evidence. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_class: Option, + /// The **closed set** of `` values this variant accepts, ascending. + /// + /// The vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm + /// takes only `400` and `429` and **rejects `404`**, even though an + /// `IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code + /// outside this set matches no branch, so the client reports a parse failure rather + /// than the error. Empty when the variant pins no exact code (see [`error_code_min`]). + /// + /// [`error_code_min`]: ResponseVariant::error_code_min + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub error_codes: Vec, + /// The `` values this variant accepts, sorted — the closed vocabulary + /// paired with [`error_codes`]. Empty when only a fallback (any-text) arm applies. + /// + /// [`error_codes`]: ResponseVariant::error_codes + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub error_texts: Vec, + /// Lower bound of the open-ended fallback arm's code range, when the variant has one + /// (`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its + /// client twin accepts 400–499). Together with [`error_code_max`] this says "any + /// code in this range is also accepted", which [`error_codes`] alone cannot express. + /// + /// [`error_code_max`]: ResponseVariant::error_code_max + /// [`error_codes`]: ResponseVariant::error_codes + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code_min: Option, + /// Upper bound of the fallback arm's code range (see [`error_code_min`]). + /// + /// [`error_code_min`]: ResponseVariant::error_code_min + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_code_max: Option, pub assertions: Vec, pub fields: Vec, } diff --git a/crates/wa-ir/src/lib.rs b/crates/wa-ir/src/lib.rs index bf87064..0f60869 100644 --- a/crates/wa-ir/src/lib.rs +++ b/crates/wa-ir/src/lib.rs @@ -62,7 +62,7 @@ pub enum Scalar { /// `waVersion` changes on every WhatsApp rollout; `schemaVersion` changes only /// when the *structure* of the IR changes. Consumers pin this to stay stable /// across WhatsApp updates. Bump on any breaking change to the IR shape. -pub const SCHEMA_VERSION: &str = "1.0.0"; +pub const SCHEMA_VERSION: &str = "1.1.0"; /// Envelope that stamps a domain IR document with [`SCHEMA_VERSION`] at emit /// time, without altering the inner document's shape. diff --git a/crates/wa-ir/src/notif.rs b/crates/wa-ir/src/notif.rs index b765657..b2d81ac 100644 --- a/crates/wa-ir/src/notif.rs +++ b/crates/wa-ir/src/notif.rs @@ -120,6 +120,122 @@ pub struct NotificationDef { /// (it delegates to a job/sub-module) — the catalog entry still stands. #[serde(default, skip_serializing_if = "Option::is_none")] pub content: Option, + /// The payload **action union** carried inside the notification, when its handler + /// dispatches on the child tag (`w:gp2` and friends — see [`NotifActionDef`]). + /// [`content`] describes the envelope; this describes what is inside it. Empty for + /// a notification whose handler carries no such per-child-tag switch. + /// + /// [`content`]: NotificationDef::content + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub actions: Vec, +} + +/// One arm of a notification's payload action union: a child tag of the +/// `` and the fields the handler reads off it. +/// +/// WA Web parses these in a `switch (child.tag())` inside the handler (e.g. +/// `WAWebHandleGroupNotification`), where each arm returns an object stamped with an +/// `actionType`. Two facts there are invisible from the wire alone and cannot be +/// derived by a consumer: +/// +/// 1. **The mapping is many-to-one.** Several wire tags normalize onto one +/// `actionType` — `ephemeral` and `not_ephemeral` both become `ephemeral`, so a +/// consumer that branches on `not_ephemeral` is writing dead code. +/// 2. **Field names are rebound.** The disappearing-message timer arrives in the +/// `expiration` attribute but the action field is called `duration` (the *create* +/// payload's spelling, `ephemeralDuration`, belongs to a different shape) — reading +/// it under the wrong name silently yields 0. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct NotifActionDef { + /// The child element's tag on the wire (`"ephemeral"`, `"not_ephemeral"`, + /// `"subject"`, …) — the value the handler switches on. + pub wire_tag: String, + /// The normalized action identity the handler stamps as `actionType`. **Not** always + /// equal to [`wire_tag`]: `not_ephemeral` maps to `ephemeral`, `locked`/`unlocked` + /// both map to `restrict`, and so on. `None` when the arm computes it dynamically + /// (e.g. `link` picks between three actions by its `link_type` attribute), which is + /// recorded rather than guessed. + /// + /// [`wire_tag`]: NotifActionDef::wire_tag + #[serde(default, skip_serializing_if = "Option::is_none")] + pub action_type: Option, + /// The fields the arm reads off the child node, in source order. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub fields: Vec, + /// Output fields the arm sets to a **constant** rather than reading from the wire — + /// `not_ephemeral` sets `duration: 0`, `locked` sets `value: true`, `unlocked` + /// `value: false`. This is exactly the normalization that is invisible from the + /// stanza, and it is what makes the many-to-one tag mapping lossless: `ephemeral` + /// vs `not_ephemeral` differ only by this. Sorted by key for determinism. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub constant_fields: Vec, + /// Repeated sub-elements the arm maps over (`revoke` → one entry per + /// ``), each with its own field list. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub children: Vec, +} + +/// One field an action arm reads off the wire. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct NotifActionField { + /// The output field name in the parsed action (`"duration"`). + pub name: String, + /// The wire attribute it is read from (`"expiration"`), or the child tag for a + /// content read. Differs from [`name`] often enough that assuming they match is the + /// bug this field exists to prevent. + /// + /// [`name`]: NotifActionField::name + pub wire_name: String, + /// How the value decodes, reusing the response-field type vocabulary. + #[serde(rename = "type")] + pub field_type: crate::ParsedFieldType, + /// Whether the arm reads the attribute unconditionally (`attrInt("expiration")`) or + /// guards on its presence (`hasAttr("reason") ? … : null`, `maybeAttrString`). + pub required: bool, + /// The element's text content is read instead of an attribute + /// (`child("body").contentString()`). + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub content: bool, +} + +/// A `key → constant` pair an action arm stamps onto its result unconditionally. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct NotifActionConstant { + pub name: String, + pub value: NotifConstValue, +} + +/// The value of a [`NotifActionConstant`] — the literal kinds a handler arm actually +/// stamps (`!0`/`!1`, `0`, a wire string). Deliberately narrower than [`crate::Scalar`] +/// (no float) so the notif IR stays `Eq`-comparable for the determinism checks. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum NotifConstValue { + Bool(bool), + Int(i64), + Str(String), +} + +/// A repeated sub-element an action arm maps over +/// (`mapChildrenWithTag("participant", …)`). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct NotifActionChild { + /// The output field the mapped list lands in (`"participants"`). + pub name: String, + /// The repeated element's wire tag (`"participant"`). + pub wire_tag: String, + /// The fields read off each element. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub fields: Vec, } /// The incoming-dispatch IR document: version stamp + the dispatcher module name + @@ -150,6 +266,7 @@ mod tests { handler_function: Some("handleDevicesNotification".into()), sub_discriminants: vec![], content: None, + actions: vec![], }; let json = serde_json::to_value(&def).unwrap(); // `notif_type` serializes under the wire name `type`. diff --git a/crates/wa-notif/Cargo.toml b/crates/wa-notif/Cargo.toml index 946ef22..a6e4356 100644 --- a/crates/wa-notif/Cargo.toml +++ b/crates/wa-notif/Cargo.toml @@ -12,6 +12,8 @@ description = "Native tooling: oxc AST extraction of WhatsApp Web's incoming sta oxc_allocator = { workspace = true } oxc_ast = { workspace = true } oxc_ast_visit = { workspace = true } +oxc_span = { workspace = true } +oxc_syntax = { workspace = true } wa-ir = { workspace = true } wa-oxc = { workspace = true } wa-scan = { workspace = true } diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs new file mode 100644 index 0000000..5f18a77 --- /dev/null +++ b/crates/wa-notif/src/actions.rs @@ -0,0 +1,756 @@ +//! The notification **payload action union** (`w:gp2` and friends). +//! +//! [`crate`] catalogues the notification envelope: which `type`s exist, which handler +//! each forwards to, and the envelope fields (`to`, `from`, `participant`, `t`, +//! `addressing_mode`). What that envelope *carries* is a second, larger union: the +//! handler maps over the notification's children and switches on each child's tag, +//! returning a differently-shaped action per tag. +//! +//! ```js +//! stanza.mapChildren(function (child) { +//! switch (child.tag()) { +//! case TAG.ADD: return { actionType: ACTIONS.ADD, participants: …, reason: … }; +//! case TAG.SUBJECT: return { actionType: ACTIONS.SUBJECT, subject: child.attrString("subject"), … }; +//! case TAG.EPHEMERAL: return { actionType: ACTIONS.EPHEMERAL, duration: child.attrInt("expiration") }; +//! case TAG.NOT_EPHEMERAL: return { actionType: ACTIONS.EPHEMERAL, duration: 0 }; // ← normalised +//! } +//! }) +//! ``` +//! +//! Two things there are unrecoverable from the wire and are what this module exists to +//! pin down. The tag→action mapping is **many-to-one** (`not_ephemeral` normalises into +//! `ephemeral` with `duration: 0`, so a `NOT_EPHEMERAL` branch in a consumer is dead +//! code), and field names are **rebound** (the timer arrives in `expiration` but the +//! action field is `duration`; reading it as `ephemeralDuration` — the *create* +//! payload's spelling — silently yields 0). +//! +//! Both the case labels and the `actionType` values are `Module.CONST.MEMBER` +//! references, so they are resolved against the defining module rather than guessed. + +use std::collections::HashMap; + +use oxc_allocator::Allocator; +use oxc_ast::ast::{Expression, Statement, SwitchStatement}; +use oxc_ast_visit::{Visit, walk}; +use wa_ir::{ + NotifActionChild, NotifActionConstant, NotifActionDef, NotifActionField, NotifConstValue, wap, +}; +use wa_oxc::{arg_expr, as_call, as_identifier, as_int, as_member, as_string_lit, callee_method}; + +/// One exported constant object, as `MEMBER → "wire value"`. +type ConstMap = HashMap; + +/// Resolves `o("Module").CONST_OBJECT.MEMBER` references to their wire string, against +/// the bundle's module slices. Memoized per `(module, object)`. +pub(crate) struct ConstResolver<'a> { + slices: &'a HashMap<&'a str, &'a str>, + cache: std::cell::RefCell>>, +} + +impl<'a> ConstResolver<'a> { + pub(crate) fn new(slices: &'a HashMap<&'a str, &'a str>) -> Self { + Self { + slices, + cache: std::cell::RefCell::new(HashMap::new()), + } + } + + /// `o("Mod").OBJECT.MEMBER` → the member's string value. + fn member(&self, module: &str, object: &str, member: &str) -> Option { + let key = (module.to_string(), object.to_string()); + if !self.cache.borrow().contains_key(&key) { + let resolved = self + .slices + .get(module) + .and_then(|slice| const_string_map(slice, object)); + self.cache.borrow_mut().insert(key.clone(), resolved); + } + self.cache.borrow()[&key] + .as_ref() + .and_then(|m| m.get(member).cloned()) + } + + /// Resolve a `o("Mod").OBJECT.MEMBER` expression, or `None` for anything else. + fn resolve(&self, e: &Expression) -> Option { + let (obj, member) = as_member(e)?; + let (owner, object) = as_member(obj)?; + let module = as_string_lit(arg_expr(as_call(owner)?.arguments.first()?)?)?; + self.member(module, object, member) + } +} + +/// Every `NAME: "value"` of a module's exported constant object, whether written as an +/// `Object.freeze({…})`, a `$InternalEnum({…})` or a bare object literal, and whether +/// exported inline (`l.TAGS = {…}`) or through a local (`var e = {…}; l.TAGS = e`). +/// +/// Deliberately its own resolver rather than [`wa_enums::resolve_named_enum`]: that one +/// is intentionally narrow (it gates `Object.freeze` behind an allowlist, because the +/// app has thousands of frozen `CONSTANT_CASE` infra objects that must not leak into the +/// *enum catalog*). Here the object is named by the switch we are already reading, so +/// there is nothing to filter — only one specific export is ever asked for. +fn const_string_map(slice: &str, export: &str) -> Option { + let alloc = Allocator::default(); + let ret = wa_oxc::parse_cjs(&alloc, slice); + if ret.panicked { + return None; + } + let mut collector = ConstCollector { + locals: HashMap::new(), + exports: HashMap::new(), + }; + collector.visit_program(&ret.program); + if let Some(direct) = collector.exports.get(export) { + return match direct { + Export::Inline(map) => Some(map.clone()), + Export::Local(name) => collector.locals.get(name).cloned(), + }; + } + None +} + +enum Export { + Inline(ConstMap), + Local(String), +} + +struct ConstCollector { + locals: HashMap, + exports: HashMap, +} + +impl<'a> Visit<'a> for ConstCollector { + fn visit_variable_declarator(&mut self, d: &oxc_ast::ast::VariableDeclarator<'a>) { + if let Some(name) = d.id.get_identifier_name() + && let Some(map) = d.init.as_ref().and_then(string_const_object) + { + self.locals.insert(name.to_string(), map); + } + walk::walk_variable_declarator(self, d); + } + + fn visit_assignment_expression(&mut self, a: &oxc_ast::ast::AssignmentExpression<'a>) { + if let Some(m) = a.left.as_member_expression() + && let Some(prop) = m.static_property_name() + { + if let Some(map) = string_const_object(&a.right) { + self.exports + .entry(prop.to_string()) + .or_insert(Export::Inline(map)); + } else if let Some(id) = as_identifier(&a.right) { + self.exports + .entry(prop.to_string()) + .or_insert(Export::Local(id.to_string())); + } + } + walk::walk_assignment_expression(self, a); + } +} + +/// An all-string-valued object literal, unwrapping the `Object.freeze(…)` / +/// `$InternalEnum(…)` / `ident(…)` single-argument wrappers WA writes them in. +/// `None` if any value isn't a string literal — a mixed object is not a token table. +fn string_const_object(e: &Expression) -> Option { + let obj = match wa_oxc::as_object(e) { + Some(o) => o, + // A single-argument wrapper call: `Object.freeze({…})`, `n("$InternalEnum")({…})`. + None => wa_oxc::as_object(arg_expr(as_call(e)?.arguments.first()?)?)?, + }; + let mut map = ConstMap::new(); + for (key, value) in wa_oxc::obj_props(obj) { + map.insert(key.to_string(), as_string_lit(value)?.to_string()); + } + (!map.is_empty()).then_some(map) +} + +/// Extract a handler module's payload action union, if it has one. +/// +/// Locates the `switch` whose cases are `Module.CONST.MEMBER` references resolving to +/// wire strings — the child-tag dispatch — and reads one [`NotifActionDef`] per arm. +/// `None` when the module has no such switch (most handlers forward straight to a +/// parser and carry no action union). +pub(crate) fn extract_actions( + handler_slice: &str, + consts: &ConstResolver, +) -> Option> { + let alloc = Allocator::default(); + let ret = wa_oxc::parse_cjs(&alloc, handler_slice); + if ret.panicked { + return None; + } + // Arms routinely delegate the bulk of their shape to a module-local helper + // (`participants: y(chat, child, tag)` is where every participant list lives), so + // the helpers are indexed up front and inlined on demand. + let mut locals = LocalFns::default(); + locals.visit_program(&ret.program); + let ctx = ArmCtx { + consts, + locals: locals + .spans + .into_iter() + .map(|(n, (a, b))| (n, handler_slice[a..b].to_string())) + .collect(), + }; + let mut finder = SwitchFinder { + ctx: &ctx, + best: None, + }; + finder.visit_program(&ret.program); + finder.best.filter(|v| !v.is_empty()) +} + +/// What an arm reader needs: the constant tables its labels resolve through, and the +/// module's local helper functions, as re-parsable source (keyed by name). +struct ArmCtx<'c, 'a> { + consts: &'c ConstResolver<'a>, + locals: HashMap, +} + +/// Collects `function name(…){…}` / `var name = function(…){…}` spans in a module. +#[derive(Default)] +struct LocalFns { + spans: Vec<(String, (usize, usize))>, +} + +impl<'a> Visit<'a> for LocalFns { + fn visit_function( + &mut self, + f: &oxc_ast::ast::Function<'a>, + flags: oxc_syntax::scope::ScopeFlags, + ) { + if let Some(id) = f.id.as_ref() { + let sp = oxc_span::GetSpan::span(f); + self.spans + .push((id.name.to_string(), (sp.start as usize, sp.end as usize))); + } + walk::walk_function(self, f, flags); + } + + fn visit_variable_declarator(&mut self, d: &oxc_ast::ast::VariableDeclarator<'a>) { + if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) + && matches!( + init, + Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) + ) + { + let sp = oxc_span::GetSpan::span(init); + self.spans + .push((name.to_string(), (sp.start as usize, sp.end as usize))); + } + walk::walk_variable_declarator(self, d); + } +} + +struct SwitchFinder<'c, 'a> { + ctx: &'c ArmCtx<'c, 'a>, + /// The richest action union seen so far (a module can hold more than one + /// const-keyed switch; the payload union is the one with the most arms). + best: Option>, +} + +impl<'a> Visit<'a> for SwitchFinder<'_, 'a> { + fn visit_switch_statement(&mut self, switch: &SwitchStatement<'a>) { + let actions = extract_switch(switch, self.ctx); + if actions.len() > self.best.as_ref().map_or(0, Vec::len) { + self.best = Some(actions); + } + walk::walk_switch_statement(self, switch); + } +} + +/// Read every arm of a const-keyed child-tag switch into [`NotifActionDef`]s. +fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec { + let mut out = Vec::new(); + for case in &switch.cases { + let Some(wire_tag) = case.test.as_ref().and_then(|t| ctx.consts.resolve(t)) else { + continue; + }; + let shapes = arm_result_shapes(&case.consequent); + if shapes.is_empty() { + // A recognised tag whose arm returns no shape (a bare flag set, an early + // break). Still catalogued: knowing the tag is dispatched at all beats + // omitting it. + out.push(empty_action(wire_tag)); + continue; + } + // An arm can branch into more than one shape. Which of the two it is matters: + // `description` branches into two DIFFERENT actions (`desc_remove` when the + // child has a ``, else `desc_add`), while `ephemeral` branches into the + // SAME action behind a feature flag, differing only in extra optional fields. + // So branches are grouped by `actionType`: different ones stay separate arms of + // the union, identical ones merge into one (a field only some branches read is + // optional in the merge). + let scope = scope_bindings(match &case.consequent[..] { + [Statement::BlockStatement(b)] => &b.body[..], + other => other, + }); + let mut grouped: Vec = Vec::new(); + for shape in shapes { + let action = read_action(wire_tag.clone(), shape, &scope, ctx, 0); + match grouped + .iter_mut() + .find(|g| g.action_type == action.action_type) + { + Some(existing) => merge_action(existing, action), + None => grouped.push(action), + } + } + out.extend(grouped); + } + out +} + +fn empty_action(wire_tag: String) -> NotifActionDef { + NotifActionDef { + wire_tag, + action_type: None, + fields: Vec::new(), + constant_fields: Vec::new(), + children: Vec::new(), + } +} + +/// Fold a second branch of the same action into the first: a field either branch reads +/// is present, and required only if BOTH read it unconditionally. A constant only one +/// branch stamps is dropped — it is not a property of the action, only of that branch. +fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { + for f in from.fields { + match into.fields.iter_mut().find(|x| x.name == f.name) { + Some(existing) => existing.required &= f.required, + None => into.fields.push(NotifActionField { + required: false, + ..f + }), + } + } + for c in from.children { + if !into.children.iter().any(|x| x.name == c.name) { + into.children.push(c); + } + } + into.constant_fields + .retain(|c| from.constant_fields.contains(c)); +} + +/// Every distinct result shape an arm can return: one per branch of a `cond ? A : B` +/// (nested ternaries included), each with its `babelHelpers.extends(…)` merged away. +fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec<&'b Expression<'a>> { + let stmts = match consequent { + [Statement::BlockStatement(b)] => &b.body[..], + other => other, + }; + let mut out = Vec::new(); + for s in stmts { + if let Statement::ReturnStatement(r) = s + && let Some(arg) = r.argument.as_ref() + { + collect_branches(arg, &mut out); + } + } + out +} + +fn collect_branches<'b, 'a>(e: &'b Expression<'a>, out: &mut Vec<&'b Expression<'a>>) { + match e { + Expression::ConditionalExpression(c) => { + collect_branches(&c.consequent, out); + collect_branches(&c.alternate, out); + } + Expression::ParenthesizedExpression(p) => collect_branches(&p.expression, out), + other => out.push(other), + } +} + +/// The `var x = ` bindings of one statement list, so a result object that names a +/// local (`{ id: n }` after `var n = jidToWid(child.attrGroupJid("jid"))`) still resolves +/// to the wire read behind it. WA's minifier hoists nearly every accessor into a local, +/// so without this most helper-built shapes would read as fieldless. +type Scope<'b, 'a> = HashMap<&'b str, &'b Expression<'a>>; + +fn scope_bindings<'b, 'a>(stmts: &'b [Statement<'a>]) -> Scope<'b, 'a> { + let mut out = Scope::new(); + for s in stmts { + if let Statement::VariableDeclaration(decl) = s { + for d in &decl.declarations { + if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) { + out.insert(name.as_str(), init); + } + } + } + } + out +} + +/// Follow a bare identifier through the scope to the expression bound to it. Bounded so +/// a self-referential minified binding can't loop. +fn deref_ident<'b, 'a>(e: &'b Expression<'a>, scope: &Scope<'b, 'a>) -> &'b Expression<'a> { + let mut cur = e; + for _ in 0..4 { + let Some(name) = as_identifier(cur) else { + return cur; + }; + match scope.get(name) { + Some(bound) if !std::ptr::eq(*bound, cur) => cur = bound, + _ => return cur, + } + } + cur +} + +/// Read one arm result shape into a [`NotifActionDef`]. +/// +/// The shape is an object literal, a `babelHelpers.extends(a, b, …)` of several, or a +/// call to a module-local helper that builds one (`return T(chat, child, author)`) — +/// all three are folded into the same definition. +fn read_action<'b, 'a>( + wire_tag: String, + result: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + ctx: &ArmCtx, + depth: u8, +) -> NotifActionDef { + let mut def = empty_action(wire_tag); + fold_shape(result, scope, &mut def, ctx, depth); + def.constant_fields.sort_by(|a, b| a.name.cmp(&b.name)); + def +} + +/// Fold one result expression's contribution into `def`. +fn fold_shape<'b, 'a>( + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + def: &mut NotifActionDef, + ctx: &ArmCtx, + depth: u8, +) { + if depth > MAX_INLINE_DEPTH { + return; + } + let e = deref_ident(e, scope); + match e { + Expression::ParenthesizedExpression(p) => fold_shape(&p.expression, scope, def, ctx, depth), + // `babelHelpers.extends(objLiteral, helper(child), …)` — every argument + // contributes; WA uses it precisely to merge a helper's fields into an arm. + Expression::CallExpression(c) if callee_method(c) == Some("extends") => { + for arg in &c.arguments { + if let Some(inner) = arg_expr(arg) { + fold_shape(inner, scope, def, ctx, depth + 1); + } + } + } + Expression::ObjectExpression(_) => fold_object(e, scope, def, ctx, depth), + _ => { + // A helper whose whole result is a repeated element (`return + // child.mapChildrenWithTag("participant", …)` — where every participant + // list lives). The caller names it after the key it was bound to. + if let Some(child) = mapped_child("", e, scope) { + def.children.push(child); + } else if let Some(src) = local_call_source(e, ctx) { + inline_local(&src, def, ctx, depth); + } + } + } +} + +/// How deep a chain of helper inlinings is followed. Three is enough for every observed +/// arm (`arm → participants helper → per-participant object`) and bounds a cycle. +const MAX_INLINE_DEPTH: u8 = 3; + +/// The source of the module-local helper `e` calls, if it is one. +fn local_call_source(e: &Expression, ctx: &ArmCtx) -> Option { + let call = as_call(e)?; + let name = as_identifier(&call.callee)?; + ctx.locals.get(name).cloned() +} + +/// Re-parse a helper's source and fold each of its result branches into `def`. +/// +/// The inlining happens inside this parse's own allocator, so no AST reference escapes +/// it — the helper's contribution is accumulated straight into `def`. +fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) { + let alloc = Allocator::default(); + // Wrapped in parens so a `function name(…){…}` declaration parses as an expression. + let wrapped = format!("({fn_src})"); + let ret = wa_oxc::parse_cjs(&alloc, &wrapped); + if ret.panicked { + return; + } + let Some(stmts) = ret.program.body.iter().find_map(|s| match s { + Statement::ExpressionStatement(es) => function_body_of(&es.expression), + _ => None, + }) else { + return; + }; + let scope = scope_bindings(stmts); + for shape in arm_result_shapes(stmts) { + fold_shape(shape, &scope, def, ctx, depth + 1); + } +} + +/// The statement list of a function expression / declaration. +fn function_body_of<'b, 'a>(e: &'b Expression<'a>) -> Option<&'b [Statement<'a>]> { + match e { + Expression::ParenthesizedExpression(p) => function_body_of(&p.expression), + Expression::FunctionExpression(f) => f.body.as_ref().map(|b| b.statements.as_slice()), + Expression::ArrowFunctionExpression(f) => Some(f.body.statements.as_slice()), + _ => None, + } +} + +/// Read the `{ key: value, … }` properties of one result shape into `def`. +fn fold_object<'b, 'a>( + obj: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + def: &mut NotifActionDef, + ctx: &ArmCtx, + depth: u8, +) { + let Some(o) = wa_oxc::as_object(obj) else { + return; + }; + for (key, value) in wa_oxc::obj_props(o) { + // The normalised action identity. Resolved through the const table, so the + // many-to-one mapping (`not_ephemeral` → `ephemeral`) is recorded as WA means + // it, not as the case label suggests. `None` when the arm computes it (e.g. + // `link` selects between three actions by its own `link_type` attribute) — + // recorded as unknown rather than guessed. + let value = deref_ident(value, scope); + if key == "actionType" { + if def.action_type.is_none() { + def.action_type = ctx.consts.resolve(value); + } + continue; + } + if let Some(c) = const_value(value) { + push_constant(def, key, c); + continue; + } + if let Some(child) = mapped_child(key, value, scope) { + if !def.children.iter().any(|x| x.name == child.name) { + def.children.push(child); + } + continue; + } + // A helper call in value position (`participants: y(chat, child, tag)`): inline + // it under this key — that is where every participant list actually lives. + if let Some(src) = local_call_source(strip_guard(value), ctx) + && depth < MAX_INLINE_DEPTH + { + let mut nested = empty_action(String::new()); + inline_local(&src, &mut nested, ctx, depth); + // A helper whose result is a repeated element becomes this key's child list; + // one that returns a flat object contributes its fields under their own names. + for mut c in nested.children { + c.name = key.to_string(); + if !def.children.iter().any(|x| x.name == c.name) { + def.children.push(c); + } + } + for f in nested.fields { + if !def.fields.iter().any(|x| x.name == f.name) { + def.fields.push(f); + } + } + continue; + } + if let Some(field) = read_field(key, value, scope) + && !def.fields.iter().any(|x| x.name == field.name) + { + def.fields.push(field); + } + } +} + +fn push_constant(def: &mut NotifActionDef, name: &str, value: NotifConstValue) { + if !def.constant_fields.iter().any(|c| c.name == name) { + def.constant_fields.push(NotifActionConstant { + name: name.to_string(), + value, + }); + } +} + +/// A literal an arm stamps unconditionally (`value: !0`, `duration: 0`, `type: "x"`). +fn const_value(e: &Expression) -> Option { + match e { + Expression::BooleanLiteral(b) => Some(NotifConstValue::Bool(b.value)), + Expression::StringLiteral(s) => Some(NotifConstValue::Str(s.value.to_string())), + // The minifier writes booleans as `!0` / `!1`. + Expression::UnaryExpression(u) if u.operator == oxc_ast::ast::UnaryOperator::LogicalNot => { + as_int(&u.argument).map(|n| NotifConstValue::Bool(n == 0)) + } + _ => as_int(e).map(NotifConstValue::Int), + } +} + +/// `child.mapChildrenWithTag("participant", function (p) { … })` → the repeated +/// sub-element and the fields read off each one. +fn mapped_child<'b, 'a>( + key: &str, + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, +) -> Option { + let call = as_call(strip_guard(e))?; + if callee_method(call)? != wap::MAP_CHILDREN_WITH_TAG { + return None; + } + let wire_tag = arg_expr(call.arguments.first()?).and_then(as_string_lit)?; + let mut fields = Vec::new(); + for arg in &call.arguments { + if let Some(e) = arg_expr(arg) { + collect_accessor_fields(e, scope, &mut fields); + } + } + Some(NotifActionChild { + name: key.to_string(), + wire_tag: wire_tag.to_string(), + fields, + }) +} + +/// Read a single output field from the expression bound to `key`. +/// +/// Handles the four shapes an arm uses: a bare accessor (`child.attrString("subject")`), +/// a presence guard (`child.hasAttr("reason") ? f(child.attrString("reason")) : null`), +/// a null-coalesce (`(x = child.maybeAttrString("t")) != null ? x : d`), and a content +/// read (`child.child("body").contentString()`). Anything else — a computed value with +/// no single wire source — yields `None` rather than a guessed name. +fn read_field<'b, 'a>( + key: &str, + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, +) -> Option { + let e = deref_ident(e, scope); + let optional_by_guard = is_guarded(e); + let (method, wire_name, content) = find_accessor(strip_guard(e), scope)?; + Some(NotifActionField { + name: key.to_string(), + wire_name, + field_type: wap::method_field_type(&method), + required: !optional_by_guard && !wap::is_optional_method(&method), + content, + }) +} + +/// Whether the value is read behind a presence guard, so the attribute may be absent. +fn is_guarded(e: &Expression) -> bool { + matches!( + e, + Expression::ConditionalExpression(_) | Expression::LogicalExpression(_) + ) +} + +/// The value branch of a `cond ? value : fallback` / `a || b` guard, else `e` itself. +fn strip_guard<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { + match e { + Expression::ConditionalExpression(c) => strip_guard(&c.consequent), + Expression::ParenthesizedExpression(p) => strip_guard(&p.expression), + Expression::LogicalExpression(l) => strip_guard(&l.left), + Expression::AssignmentExpression(a) => strip_guard(&a.right), + _ => e, + } +} + +/// The innermost wap accessor call in `e` → `(method, wireName, isContentRead)`. +/// +/// Descends through the wrapper calls an arm applies to a raw wire value +/// (`userJidToUserWid(child.attrUserJid("jid"))`, `S(child.attrString("reason"))`), and +/// through a `child("body").contentString()` chain — where the *tag* is the wire name, +/// since a content read has no attribute of its own. +fn find_accessor<'b, 'a>( + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, +) -> Option<(String, String, bool)> { + let call = as_call(deref_ident(e, scope))?; + if let Some(method) = callee_method(call) { + let arg0 = call.arguments.first().and_then(arg_expr); + let name = arg0.and_then(as_string_lit); + if let Some(name) = name + && is_wire_accessor(method) + { + return Some((method.to_string(), name.to_string(), false)); + } + // `X.contentString()` / `X.contentInt()` — no argument; the wire name is the tag + // of whatever `X` descends to, and `""` when it reads the arm's own node. + if matches!( + method, + wap::CONTENT_STRING | wap::CONTENT_INT | wap::CONTENT_BYTES + ) { + let tag = wa_oxc::callee_object(call) + .and_then(as_call) + .and_then(|inner| inner.arguments.first().and_then(arg_expr)) + .and_then(as_string_lit) + .unwrap_or_default(); + return Some((method.to_string(), tag.to_string(), true)); + } + } + // A wrapper call (`userJidToUserWid(…)`, a local normaliser): look inside. + call.arguments + .iter() + .filter_map(arg_expr) + .find_map(|a| find_accessor(a, scope)) +} + +/// Whether `method` is a wap accessor that reads a named wire attribute. Keyed on the +/// accessor prefix rather than an exhaustive list so a new JID/time flavour is covered +/// automatically; `hasAttr` is excluded because it is the *guard*, not the read. +fn is_wire_accessor(method: &str) -> bool { + (method.starts_with("attr") || method.starts_with("maybeAttr")) && method != wap::HAS_ATTR +} + +/// Collect every wire accessor reachable inside a mapped-child callback, as fields. +/// Order follows source order; duplicates (the same attribute read twice, e.g. once for +/// a derived flag) are folded into the first occurrence. +fn collect_accessor_fields<'b, 'a>( + e: &'b Expression<'a>, + outer: &Scope<'b, 'a>, + out: &mut Vec, +) { + // The callback's own `var` bindings, layered over the enclosing scope: the minifier + // hoists most reads into locals inside the callback too. + let mut scope = outer.clone(); + if let Some(stmts) = function_body_of(e) { + scope.extend(scope_bindings(stmts)); + } + struct Walker<'o, 'b, 'a> { + scope: &'o Scope<'b, 'a>, + out: &'o mut Vec, + } + impl<'b, 'a> Visit<'a> for Walker<'_, 'b, 'a> { + fn visit_object_property(&mut self, p: &oxc_ast::ast::ObjectProperty<'a>) { + if let Some(key) = wa_oxc::property_key_name(&p.key) + && let Some(field) = read_field(key, &p.value, self.scope) + && !self.out.iter().any(|f| f.name == field.name) + { + self.out.push(field); + } + walk::walk_object_property(self, p); + } + } + // Only function arguments hold the per-element shape; a bare expression contributes + // nothing (the tag string, the bounds). + if matches!( + e, + Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) + ) { + let mut w = Walker { scope: &scope, out }; + w.visit_expression(e); + } + // A callback that returns a bare value rather than an object + // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the + // field by, so the wire attribute names it — better than reporting no fields at all. + if out.is_empty() + && let Some(stmts) = function_body_of(e) + { + for shape in arm_result_shapes(stmts) { + if let Some((method, wire_name, content)) = find_accessor(shape, &scope) { + out.push(NotifActionField { + name: wire_name.clone(), + wire_name, + field_type: wap::method_field_type(&method), + required: !wap::is_optional_method(&method), + content, + }); + } + } + } +} diff --git a/crates/wa-notif/src/lib.rs b/crates/wa-notif/src/lib.rs index 5e69631..952a484 100644 --- a/crates/wa-notif/src/lib.rs +++ b/crates/wa-notif/src/lib.rs @@ -31,6 +31,8 @@ //! the AST. #![cfg(not(target_arch = "wasm32"))] +mod actions; + use std::collections::HashMap; use oxc_allocator::Allocator; @@ -123,6 +125,25 @@ pub fn extract_notif_from_modules( n.content = notification_content(slice, ¬if_type); } + // Phase 3: the payload action union. `content` describes the envelope; a handler + // that maps over the notification's children and switches on each child's tag + // carries a whole second union inside it (`w:gp2`'s 40+ group actions). Resolved + // against the full module index, since both the case labels and the `actionType` + // values are `Module.CONST.MEMBER` references defined elsewhere. + let consts = actions::ConstResolver::new(&slice_by_name); + for n in &mut dispatch.notifications { + let Some(slice) = n + .handler_module + .as_deref() + .and_then(|m| slice_by_name.get(m)) + else { + continue; + }; + if let Some(found) = actions::extract_actions(slice, &consts) { + n.actions = found; + } + } + // Phase 2b: a type still degraded here has a handler that delegates parsing to // the smax receive-RPC path rather than an inline `WADeprecatedWapParser`, so its // field tree lives in a `WASmaxIn...Request` module (the srvreq domain). @@ -435,6 +456,7 @@ fn extract_notifications(consequent: &[Statement]) -> Vec { handler_function: handler.and_then(|(_, f)| f), sub_discriminants: Vec::new(), content: None, + actions: Vec::new(), }); } } @@ -450,6 +472,7 @@ fn extract_notification_case(notif_type: &str, consequent: &[Statement]) -> Noti handler_function: handler.and_then(|(_, f)| f), sub_discriminants: extract_sub_discriminants(consequent), content: None, + actions: Vec::new(), } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index bd9ded5..9921783 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -564,3 +564,178 @@ __d("WASmaxInCoexistenceOffboardingNotificationRequest",["WAResultOrError","WASm "an ambiguous type (two read-shapes) must stay degraded" ); } + +/// A miniature `w:gp2` handler: the const tag/action tables in their own modules, and a +/// handler that maps over the notification's children and switches on each child's tag. +/// Reproduces every shape the real one uses — a many-to-one normalisation, a rebound +/// field name, a constant-only arm, a repeated sub-element, and a two-outcome ternary. +const GROUP_ACTIONS_BUNDLE: &str = r#" +__d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g,r,d,o,e,i,l){ + l.handle = function(){ return (function*(e,t){ + var n = e.attrs; + switch (e.tag) { + case "notification": + switch (n.type) { + case "w:gp2": return yield r("WAWebHandleGroupNotification")(e); + } + } + }); }; +}, 1); +__d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description"}); + l.GROUP_NOTIFICATION_TAG=e; +}), 1); +__d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ + var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); + l.GROUP_ACTIONS=d; +}), 1); +__d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ + function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ + return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name") }; + }); } + function h(e){ + var x = e.mapChildren(function(t){ + var a = t.tag(); + switch(a){ + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.ADD: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ADD, participants:y(e,t), reason:t.hasAttr("reason")?t.attrString("reason"):null}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUBJECT: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SUBJECT, subject:t.attrString("subject")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EPHEMERAL: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:0}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.REVOKE_INVITE: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.REVOKE_INVITE, participants:t.mapChildrenWithTag("participant", function(p){ return {id:p.attrUserJid("jid"), expiration:p.attrInt("expiration")}; })}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DESC: + return t.hasChild("delete") ? {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:t.attrString("id")} + : {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_ADD, descId:t.attrString("id"), desc:t.child("body").contentString()}; + } + }); + return {actions:x}; + } + l.handleGroupNotification=h; +}), 1); +"#; + +/// The `w:gp2` action union, keyed by the wire tag of each arm. +fn group_actions(ir: &wa_ir::NotifIr) -> Vec<&wa_ir::NotifActionDef> { + notif(ir, "w:gp2").actions.iter().collect() +} + +#[test] +fn group_action_union_records_the_many_to_one_tag_mapping() { + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let actions = group_actions(&ir); + let of = |tag: &str| { + actions + .iter() + .filter(|a| a.wire_tag == tag) + .collect::>() + }; + // Acceptance criterion: which wire tags map to `ephemeral`, and what each sets. + let eph = of("ephemeral"); + assert_eq!(eph.len(), 1); + assert_eq!(eph[0].action_type.as_deref(), Some("ephemeral")); + let duration = eph[0] + .fields + .iter() + .find(|f| f.name == "duration") + .expect("duration field"); + // The timer arrives in `expiration`, NOT under the output name — the whole point. + assert_eq!(duration.wire_name, "expiration"); + assert_eq!(duration.field_type, wa_ir::ParsedFieldType::Integer); + assert!(duration.required); + // `not_ephemeral` normalises INTO `ephemeral` with a constant 0 — a consumer that + // branches on a `not_ephemeral` action type is writing dead code. + let not_eph = of("not_ephemeral"); + assert_eq!(not_eph.len(), 1); + assert_eq!(not_eph[0].action_type.as_deref(), Some("ephemeral")); + assert!(not_eph[0].fields.is_empty()); + assert_eq!( + not_eph[0].constant_fields, + vec![wa_ir::NotifActionConstant { + name: "duration".into(), + value: wa_ir::NotifConstValue::Int(0), + }] + ); + // `locked` renames too, and stamps a boolean constant. + let locked = of("locked"); + assert_eq!(locked[0].action_type.as_deref(), Some("restrict")); + assert_eq!( + locked[0].constant_fields[0].value, + wa_ir::NotifConstValue::Bool(true) + ); +} + +#[test] +fn group_action_arms_recover_fields_children_and_branches() { + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let actions = group_actions(&ir); + let one = |tag: &str| { + actions + .iter() + .find(|a| a.wire_tag == tag) + .unwrap_or_else(|| panic!("no arm for {tag}")) + }; + // A presence-guarded attr is optional; an unguarded one is required. + let add = one("add"); + let reason = add + .fields + .iter() + .find(|f| f.name == "reason") + .expect("reason"); + assert!(!reason.required, "hasAttr(…) ? … : null → optional"); + // The participant list lives behind a module-local helper; it must still be + // recovered, with the per-participant fields. + let participants = add + .children + .iter() + .find(|c| c.name == "participants") + .expect("participants child"); + assert_eq!(participants.wire_tag, "participant"); + let names: Vec<&str> = participants + .fields + .iter() + .map(|f| f.name.as_str()) + .collect(); + assert_eq!(names, ["id", "displayName"]); + assert_eq!( + participants.fields[0].field_type, + wa_ir::ParsedFieldType::UserJid + ); + // An inline (non-helper) mapped child works the same way. + assert_eq!(one("revoke").children[0].wire_tag, "participant"); + // An arm that branches into two DIFFERENT actions yields two arms of the union, + // sharing the wire tag — not one arm with the first branch's shape. + let desc: Vec<_> = actions + .iter() + .filter(|a| a.wire_tag == "description") + .collect(); + let types: Vec<&str> = desc + .iter() + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!(types, ["desc_remove", "desc_add"]); + // The `` content read is flagged as content, with the tag as its wire name. + let body = desc[1] + .fields + .iter() + .find(|f| f.name == "desc") + .expect("desc"); + assert!(body.content); + assert_eq!(body.wire_name, "body"); +} + +#[test] +fn handler_without_a_const_keyed_switch_has_no_actions() { + // Most handlers forward straight to a parser; they must not sprout a phantom union. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + for n in &ir.notifications { + if n.notif_type != "w:gp2" { + assert!(n.actions.is_empty(), "{} grew actions", n.notif_type); + } + } +} diff --git a/crates/wa-scan/Cargo.toml b/crates/wa-scan/Cargo.toml index c47a137..b0e221c 100644 --- a/crates/wa-scan/Cargo.toml +++ b/crates/wa-scan/Cargo.toml @@ -22,3 +22,4 @@ wa-transform = { workspace = true } [dev-dependencies] anyhow = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index a9cb8b5..1278625 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -67,6 +67,21 @@ fn single_namespace_content_tags(stanzas: &[IqStanzaDef]) -> HashSet { .collect() } +/// Extraction-quality counters the IQ scan hands back alongside the IR. +/// +/// Split from [`IqIr`] on purpose: these are *about* the extraction, not part of the +/// contract, and they exist so a silent regression (a WA refactor that hides a +/// construct we key on) shows up as a number rather than as a quietly emptier field. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct IqScanStats { + /// Phase-2 fragment-merge recovery — see [`CrossModuleStats`]. + pub cross_module: CrossModuleStats, + /// Constraints seen in a response parser but not structurally resolvable, by reason + /// (an un-pinnable literal value, a computed reference path, an unresolvable enum + /// argument). Folded into `manifest.diagnostics.iq.dropsByReason`. + pub constraint_drops: std::collections::BTreeMap, +} + /// Cross-module (`mergeStanzas`, Phase 2) recovery counters for the IQ domain, /// surfaced in `manifest.diagnostics.iq.crossModule`. They make the Phase-2 fix /// visible and regressable: if a future bundle/codegen change silently broke @@ -120,12 +135,12 @@ pub fn scan_iq_stanzas_from_modules( scan_iq_with_diagnostics(source, module_defs).0 } -/// Like [`scan_iq_stanzas_from_modules`], but also returns the cross-module -/// (Phase 2) recovery counters for `manifest.diagnostics.iq.crossModule`. +/// Like [`scan_iq_stanzas_from_modules`], but also returns the extraction-quality +/// counters for `manifest.diagnostics.iq`. pub fn scan_iq_with_diagnostics( source: &str, module_defs: &[ModuleDefinition], -) -> (IqScanResult, CrossModuleStats) { +) -> (IqScanResult, IqScanStats) { // Build the cross-module helper index first. It resolves wap-returning helpers // (`xmppSignedPreKey`, `xmppPreKey`, …) so a child built via `o("Module").fn(...)` // / `.map(o("Module").fn)` recovers its subtree instead of leaving a bare node @@ -143,6 +158,7 @@ pub fn scan_iq_with_diagnostics( // `WASmaxIn*ResponseSuccess` modules so a Request can attach its typed // response (the smax response lives in a separate module). let response_index = response_index::build_pass(module_defs, source); + let constraint_drops = response_index.drop_counts().clone(); // Build the content-length cross-reference once. It reads the byte length WA // Web's parsers pin each wire field to (`child("signature").contentBytes(64)`), @@ -164,7 +180,8 @@ pub fn scan_iq_with_diagnostics( let mut stanzas = Vec::new(); let mut unparseable = Vec::new(); - let mut cross = CrossModuleStats::default(); + let mut stats = IqScanStats::default(); + let cross = &mut stats.cross_module; // The same module is often defined in several bundle shards (FB_PKG_DELIM); // scan each module name once so identical stanzas aren't emitted per shard // (and we don't redo the work). @@ -274,7 +291,10 @@ pub fn scan_iq_with_diagnostics( stanzas, unparseable, }, - cross, + IqScanStats { + constraint_drops, + ..stats + }, ) } @@ -298,22 +318,21 @@ pub fn extract_iq_from_modules( extract_iq_from_modules_with_diagnostics(source, module_defs, wa_version).0 } -/// Like [`extract_iq_from_modules`], but also returns the cross-module (Phase 2) -/// recovery counters so the pipeline can record them under -/// `manifest.diagnostics.iq.crossModule`. +/// Like [`extract_iq_from_modules`], but also returns the extraction-quality counters +/// so the pipeline can record them under `manifest.diagnostics.iq`. pub fn extract_iq_from_modules_with_diagnostics( source: &str, module_defs: &[ModuleDefinition], wa_version: &str, -) -> (IqIr, CrossModuleStats) { - let (scan, cross) = scan_iq_with_diagnostics(source, module_defs); +) -> (IqIr, IqScanStats) { + let (scan, stats) = scan_iq_with_diagnostics(source, module_defs); ( IqIr { wa_version: wa_version.to_string(), stanzas: scan.stanzas, unparseable: scan.unparseable, }, - cross, + stats, ) } @@ -410,9 +429,9 @@ mod tests { // Diagnostics: exactly the Request folds in a mixin and is enriched by it; // only `spam_flow` is new (`jid` was already local), so one field recovered. - assert_eq!(cross.requests_with_mixins, 1); - assert_eq!(cross.requests_enriched, 1); - assert_eq!(cross.fields_recovered, 1); + assert_eq!(cross.cross_module.requests_with_mixins, 1); + assert_eq!(cross.cross_module.requests_enriched, 1); + assert_eq!(cross.cross_module.fields_recovered, 1); } #[test] diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 26a5e58..346e5c1 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -372,6 +372,7 @@ impl ParserAnalyzer<'_, '_> { kind: AssertionKind::Tag, name: Some(v.to_string()), value: None, + reference_path: None, }); } } @@ -381,6 +382,7 @@ impl ParserAnalyzer<'_, '_> { kind: AssertionKind::Attr, name: Some(name.to_string()), value: arg_str(call, 1).map(str::to_string), + reference_path: None, }); } } @@ -388,6 +390,7 @@ impl ParserAnalyzer<'_, '_> { kind: AssertionKind::FromServer, name: None, value: None, + reference_path: None, }), _ => {} } diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 6bdd8b8..bb09dd8 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -12,7 +12,10 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use wa_ir::{ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, ResponseVariantKind}; +use wa_ir::{ + ErrorClass, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, ResponseVariantKind, + UnionVariant, +}; use crate::response_smax::{Resolved, Resolver, analyze_module_exports, scan_cascade_variants}; use wa_transform::ModuleDefinition; @@ -27,9 +30,19 @@ pub struct ResponseIndex { /// shapes, so an op whose response module ends differently (e.g. PingsClient's /// `…ResponseServerResponse`) needs a lookup that anchors on the known op name. in_slices: Vec<(String, String)>, + /// Constraints (literal values, reference paths, enum arguments) that were present + /// in a parser but not structurally resolvable, by reason. Surfaced under + /// `manifest.diagnostics.iq.dropsByReason` so "no constraint here" and "a constraint + /// we failed to extract" never look alike to a consumer. + drops: BTreeMap, } impl ResponseIndex { + /// See [`ResponseIndex::drops`]. + pub(crate) fn drop_counts(&self) -> &BTreeMap { + &self.drops + } + /// Look up the response for operation `x` (derived from a request module name). pub(crate) fn get_by_x(&self, x: &str) -> Option<&ParsedResponse> { self.by_x.get(x) @@ -121,10 +134,21 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd // recovered separately since the JS keeps them as parser asserts, not fields. // These let codegen guard each arm so the outcome union doesn't misclassify. let assertions = resolver.assertions(&module, &func); + // The per-RPC error vocabulary: which `` pairs THIS RPC's + // error arm accepts. It is a closed set and it differs per RPC, so it can + // only be read off the arm's own error disjunction. + let vocab = error_vocabulary(&fields); variants.push(ResponseVariant { tag, module_name: module, kind, + error_class: (kind == ResponseVariantKind::Error) + .then(|| vocab.class()) + .flatten(), + error_codes: vocab.codes, + error_texts: vocab.texts, + error_code_min: vocab.code_min, + error_code_max: vocab.code_max, assertions, fields, }); @@ -165,7 +189,111 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd .map(|(n, s)| (n.to_string(), s.to_string())) .collect(); - ResponseIndex { by_x, in_slices } + ResponseIndex { + by_x, + in_slices, + drops: resolver.drop_counts(), + } +} + +/// The `` codes/texts one response variant accepts, gathered from its error +/// disjunction. +#[derive(Default)] +struct ErrorVocabulary { + codes: Vec, + texts: Vec, + code_min: Option, + code_max: Option, +} + +impl ErrorVocabulary { + /// Which side the codes blame: 4xx is the client's fault, 5xx the server's. Derived + /// from the evidence rather than the variant's name, since WA spells the two arms + /// inconsistently (`…ResponseServerError` but also `…ResponseInternalServerError`). + /// `None` when the arm pins no code at all, or mixes both families (never guessed). + fn class(&self) -> Option { + let of = |c: i64| match c { + 400..=499 => Some(ErrorClass::Client), + 500..=599 => Some(ErrorClass::Server), + _ => None, + }; + let mut seen: Option = None; + for c in self + .codes + .iter() + .copied() + .chain(self.code_min) + .chain(self.code_max) + { + match (of(c), seen) { + (None, _) => return None, + (Some(cur), None) => seen = Some(cur), + (Some(cur), Some(prev)) if cur == prev => {} + _ => return None, + } + } + seen + } +} + +/// Collect the `` vocabulary from a variant's resolved fields. +/// +/// An error variant's payload is an `errorXxxErrors` union field whose alternatives are +/// the per-namespace error mixins; each mixin pins its own `text`/`code` +/// (`literal(attrString, e, "text", "rate-overlimit")` + `literal(attrInt, e, "code", +/// 429)`), except the fallback arms, which range-check the code instead +/// (`attrIntRange(e, "code", 500, 599)`). Both forms are already carried on the fields +/// (as `literal_value` / `int_min`+`int_max`), so this reads them back rather than +/// re-parsing the bundle. +fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { + let mut v = ErrorVocabulary::default(); + collect_error_vocabulary(fields, &mut v); + v.codes.sort_unstable(); + v.codes.dedup(); + v.texts.sort(); + v.texts.dedup(); + v +} + +fn collect_error_vocabulary(fields: &[ParsedField], out: &mut ErrorVocabulary) { + for f in fields { + if let Some(variants) = &f.union_variants { + for uv in variants { + collect_variant_vocabulary(uv, out); + } + } + if let Some(children) = &f.children { + collect_error_vocabulary(children, out); + } + } +} + +fn collect_variant_vocabulary(uv: &UnionVariant, out: &mut ErrorVocabulary) { + for f in &uv.fields { + match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { + ("code", Some(lit)) => { + if let Ok(code) = lit.parse::() { + out.codes.push(code); + } + } + ("text", Some(lit)) => out.texts.push(lit.clone()), + // A fallback arm accepts any text within a code RANGE (400–499 / 500–599), + // which no enumeration of exact codes can express. + ("code", None) => { + if let (Some(min), Some(max)) = (f.int_min, f.int_max) { + out.code_min = Some(out.code_min.map_or(min, |cur: i64| cur.min(min))); + out.code_max = Some(out.code_max.map_or(max, |cur: i64| cur.max(max))); + } + } + _ => {} + } + // A variant that nests a further disjunction (union-of-unions). + if let Some(nested) = &f.union_variants { + for inner in nested { + collect_variant_vocabulary(inner, out); + } + } + } } /// A discriminated-union field carrying `variants`. @@ -253,6 +381,144 @@ mod tests { assert_eq!(pr.fields[0].name, "id"); } + /// A minimal RPC with one client-error arm over two error mixins, plus the + /// open-ended server fallback — the shape every namespace repeats. + const ERROR_RPC: &str = r#" + __d("WASmaxInFooIQErrorBadRequestMixin",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxParseUtils").assertTag(e, "error"); if(!t.success) return t; + var n = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, e, "text", "bad-request"); if(!n.success) return n; + var r = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrInt, e, "code", 400); + return r.success ? o("WAResultOrError").makeResult({text:n.value,code:r.value}) : r; + } + l.parseIQErrorBadRequestMixin = e; + }), 98); + __d("WASmaxInFooIQErrorFallbackServerMixin",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxParseUtils").assertTag(e, "error"); if(!t.success) return t; + var n = o("WASmaxParseUtils").attrString(e, "text"); if(!n.success) return n; + var r = o("WASmaxParseUtils").attrIntRange(e, "code", 500, 599); + return r.success ? o("WAResultOrError").makeResult({text:n.value,code:r.value}) : r; + } + l.parseIQErrorFallbackServerMixin = e; + }), 98); + __d("WASmaxInFooClientErrors",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxInFooIQErrorBadRequestMixin").parseIQErrorBadRequestMixin(e); + if(t.success) return o("WAResultOrError").makeResult({name:"IQErrorBadRequest",value:t.value}); + return o("WASmaxParseUtils").errorMixinDisjunction(e,["IQErrorBadRequest"],[t]); + } + l.parseClientErrors = e; + }), 98); + __d("WASmaxInFooServerErrors",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxInFooIQErrorFallbackServerMixin").parseIQErrorFallbackServerMixin(e); + if(t.success) return o("WAResultOrError").makeResult({name:"IQErrorFallbackServer",value:t.value}); + return o("WASmaxParseUtils").errorMixinDisjunction(e,["IQErrorFallbackServer"],[t]); + } + l.parseServerErrors = e; + }), 98); + __d("WASmaxInFooGetBarResponseSuccess",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e,t){ + var n = o("WASmaxParseUtils").assertTag(e, "iq"); if(!n.success) return n; + var s = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, e, "type", "result"); + return s.success ? o("WAResultOrError").makeResult({type:s.value}) : s; + } + l.parseGetBarResponseSuccess = e; + }), 98); + __d("WASmaxInFooGetBarResponseClientError",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e,t){ + var n = o("WASmaxParseUtils").assertTag(e, "iq"); if(!n.success) return n; + var r = o("WASmaxParseUtils").flattenedChildWithTag(e, "error"); if(!r.success) return r; + var i = o("WASmaxInFooClientErrors").parseClientErrors(r.value); + return i.success ? o("WAResultOrError").makeResult({errorClientErrors:i.value}) : i; + } + l.parseGetBarResponseClientError = e; + }), 98); + __d("WASmaxInFooGetBarResponseServerError",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e,t){ + var n = o("WASmaxParseUtils").assertTag(e, "iq"); if(!n.success) return n; + var r = o("WASmaxParseUtils").flattenedChildWithTag(e, "error"); if(!r.success) return r; + var i = o("WASmaxInFooServerErrors").parseServerErrors(r.value); + return i.success ? o("WAResultOrError").makeResult({errorServerErrors:i.value}) : i; + } + l.parseGetBarResponseServerError = e; + }), 98); + __d("WASmaxFooGetBarRPC",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e,t){ + var n = o("WASmaxInFooGetBarResponseSuccess").parseGetBarResponseSuccess(e,t); + if(n.success) return o("WAResultOrError").makeResult({name:"GetBarResponseSuccess",value:n.value}); + var r = o("WASmaxInFooGetBarResponseClientError").parseGetBarResponseClientError(e,t); + if(r.success) return o("WAResultOrError").makeResult({name:"GetBarResponseClientError",value:r.value}); + var a = o("WASmaxInFooGetBarResponseServerError").parseGetBarResponseServerError(e,t); + return a.success ? o("WAResultOrError").makeResult({name:"GetBarResponseServerError",value:a.value}) : a; + } + l.default = e; + }), 98);"#; + + #[test] + fn variant_carries_its_closed_error_vocabulary() { + let defs = wa_transform::extract_module_definitions(ERROR_RPC); + let idx = build_pass(&defs, ERROR_RPC); + let pr = idx.get_by_x("FooGetBar").expect("indexed by X"); + let v = |tag: &str| { + pr.variants + .iter() + .find(|v| v.tag == tag) + .unwrap_or_else(|| panic!("no variant {tag}")) + }; + // A success variant carries no error vocabulary and no error class. + let ok = v("GetBarResponseSuccess"); + assert_eq!(ok.kind, ResponseVariantKind::Success); + assert!(ok.error_class.is_none()); + assert!(ok.error_codes.is_empty() && ok.error_texts.is_empty()); + // The client arm's vocabulary is CLOSED: exactly what its disjunction accepts. + // Answering `404 item-not-found` here matches no branch — the bug this exists + // to make visible — so 404 must not appear. + let ce = v("GetBarResponseClientError"); + assert_eq!(ce.error_class, Some(ErrorClass::Client)); + assert_eq!(ce.error_codes, vec![400]); + assert_eq!(ce.error_texts, vec!["bad-request".to_string()]); + assert_eq!((ce.error_code_min, ce.error_code_max), (None, None)); + // The server arm is the open-ended fallback: any text, any code in 500..=599. + let se = v("GetBarResponseServerError"); + assert_eq!(se.error_class, Some(ErrorClass::Server)); + assert!(se.error_codes.is_empty(), "a range is not an exact code"); + assert_eq!( + (se.error_code_min, se.error_code_max), + (Some(500), Some(599)) + ); + } + + #[test] + fn error_class_is_derived_from_codes_not_from_the_variant_name() { + // Codes decide, so `…ResponseInternalServerError` (which reads like a server + // arm but is spelled inconsistently across namespaces) can't be misfiled. + let v = ErrorVocabulary { + codes: vec![400, 429], + ..Default::default() + }; + assert_eq!(v.class(), Some(ErrorClass::Client)); + let v = ErrorVocabulary { + code_min: Some(500), + code_max: Some(599), + ..Default::default() + }; + assert_eq!(v.class(), Some(ErrorClass::Server)); + // Mixed families, or a code outside both, is not classified — never guessed. + let v = ErrorVocabulary { + codes: vec![400, 500], + ..Default::default() + }; + assert_eq!(v.class(), None); + let v = ErrorVocabulary { + codes: vec![304], + ..Default::default() + }; + assert_eq!(v.class(), None); + assert_eq!(ErrorVocabulary::default().class(), None); + } + #[test] fn build_pass_recovers_nested_child_response() { // A ResponseSuccess whose payload is a child parsed by a local fn — the diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index acad31b..adc4e7c 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -33,14 +33,15 @@ //! to avoid any regression to its 33 stanzas / tests. use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use oxc_allocator::Allocator; use oxc_ast::ast::{Argument, CallExpression, Expression, Function, Statement}; use oxc_span::GetSpan; use wa_ir::wap; use wa_ir::{ - AssertionKind, ParsedField, ParsedFieldType, ParsedResponse, ResponseAssertion, UnionVariant, + AssertionKind, AttrEnumRef, AttrEnumVariant, ParsedField, ParsedFieldType, ParsedResponse, + ResponseAssertion, Scalar, UnionVariant, }; use wa_oxc::{arg_expr, as_call, as_identifier, as_int, as_string_lit, callee_method}; @@ -73,6 +74,13 @@ pub(crate) struct Resolver<'a> { /// holds fields/unions); see [`Resolver::assertions`]. assert_cache: RefCell>>, assert_in_progress: RefCell>, + /// Memoized `(module, enum)` → resolved wire enum, for the enum argument of an + /// `attrStringEnum`/`contentStringEnum` accessor; see [`Resolver::resolve_enum`]. + enum_cache: RefCell>>, + /// Constraints seen but **not** structurally resolvable, by reason. Surfaced under + /// `manifest.diagnostics.iq.dropsByReason` so a consumer can tell "this field carries + /// no constraint" from "a constraint was there and we failed to extract it". + drops: RefCell>, } impl<'a> Resolver<'a> { @@ -83,9 +91,66 @@ impl<'a> Resolver<'a> { in_progress: RefCell::new(HashSet::new()), assert_cache: RefCell::new(HashMap::new()), assert_in_progress: RefCell::new(HashSet::new()), + enum_cache: RefCell::new(HashMap::new()), + drops: RefCell::new(BTreeMap::new()), } } + /// The constraints this resolver saw but could not resolve, by reason (see + /// [`Resolver::drops`]). Snapshot; safe to call after a scan. + pub(crate) fn drop_counts(&self) -> BTreeMap { + self.drops.borrow().clone() + } + + /// Record one unresolvable constraint under `reason`. + fn drop_note(&self, reason: &str) { + *self + .drops + .borrow_mut() + .entry(reason.to_string()) + .or_insert(0) += 1; + } + + /// Resolve the enum argument of an enum accessor (`o("WASmaxInFooEnums").ENUM_OFF_ON`) + /// to its full `(name, module, variants)`. Reuses [`wa_enums::resolve_named_enum`], the + /// same resolver the request side uses, so both halves of the protocol type an enum + /// attribute identically. `None` — never a guess — when the module isn't in the bundle, + /// the export isn't a resolvable enum, or any variant value isn't a string (every + /// stanza-attr enum is a wire-token enum). + fn resolve_enum(&self, module: &str, name: &str) -> Option { + let key = (module.to_string(), name.to_string()); + if let Some(hit) = self.enum_cache.borrow().get(&key) { + return hit.clone(); + } + let resolved = self + .slices + .get(module) + .and_then(|slice| wa_enums::resolve_named_enum(slice, module, name)) + .and_then(|def| { + let variants: Vec = def + .variants + .into_iter() + .map(|v| match v.value { + Scalar::Str(s) => Some(AttrEnumVariant { + name: v.name, + value: s, + }), + _ => None, + }) + .collect::>>()?; + (!variants.is_empty()).then(|| AttrEnumRef { + name: name.to_string(), + module: module.to_string(), + variants, + }) + }); + if resolved.is_none() { + self.drop_note("response enum argument not structurally resolvable"); + } + self.enum_cache.borrow_mut().insert(key, resolved.clone()); + resolved + } + /// Resolve `o(module).func(…)` to its fields or union, or `None` if the module /// is absent, the fn isn't found, or a cycle is hit. pub(crate) fn resolve(&self, module: &str, func: &str) -> Option { @@ -205,6 +270,28 @@ enum Binding { /// `flattenedChildWithTag` descent (`attrString(n.value, "id")` reads off the /// `` child `n`, not the parent node). source_path: Option>, + /// The fixed value a `literal`/`optionalLiteral` wrapper pins this accessor to + /// (`literal(attrString, e, "type", "admin")` → `"admin"`), stringified. + literal_value: Option, + /// The wire enum the accessor validates the value against, for + /// `attrStringEnum`/`contentStringEnum`. + enum_ref: Option, + /// The request path an `optionalLiteral(…, ref.value)` echo pins the value to. + /// Its required twin is recorded as a `Reference` assertion instead (an optional + /// pin guards nothing, so it is not an assertion). + reference_path: Option>, + }, + /// A value read off the **request** rather than the response node — + /// `o("WASmaxParseReference").attrStringFromReference(request, ["to"])`. `path` is + /// the helper's path argument (wrappers to descend, then the attribute name). The + /// binding doubles as a [`Binding::Field`]: a reference var is usually consumed only + /// by a `literal(…, ref.value)` echo guard, but when the tail names it, it is a real + /// output field whose value happens to come from the request. + Reference { + path: Vec, + method: String, + field_type: ParsedFieldType, + required: bool, }, /// A cross-module parser call (`o(mod).parse(node)`) that resolved to a /// flat field list: a same-node payload mixin or a plain sub-parser. @@ -425,6 +512,7 @@ fn classify_call( kind: AssertionKind::Tag, name: Some(tag.to_string()), value: None, + reference_path: None, }); } Binding::None @@ -436,6 +524,7 @@ fn classify_call( kind: AssertionKind::Attr, name: name.map(str::to_string), value: value.map(str::to_string), + reference_path: None, }); Binding::None } @@ -445,19 +534,40 @@ fn classify_call( // wrapped accessor; when it's only an assertion (unreferenced) it emits // nothing. `literalContent` pins a constant string (no makeResult value). "literal" => { - // `literal(ACC, node, "attr", "fixedValue")` pins the attr to a constant — - // a hard discriminator when the value is a string literal on the SAME node - // (`type:"result"`). A reference value (`literal(…, "id", r.value)`) is - // request-relative, not a fixed discriminator, so skip it. + // The pinned value is one of three things: + // - a compile-time literal (`"result"`, `429`) → a hard discriminator, and + // the value the field carries; + // - a value read from the REQUEST (`r.value` where `r` came from + // `attrStringFromReference(request, ["to"])`) → an *echo* rule: the attr + // must equal that request field, which is not a constant but is every bit + // as binding on an emitter; + // - anything else → not statically resolvable; counted, never guessed. + let attr = args.get(2).and_then(arg_expr).and_then(as_string_lit); + let literal_value = args.get(3).and_then(arg_expr).and_then(static_literal); + let reference_path = args + .get(3) + .and_then(arg_expr) + .and_then(|e| reference_path_of(e, bindings)); if source_path.is_none() - && let Some(attr) = args.get(2).and_then(arg_expr).and_then(as_string_lit) - && let Some(value) = args.get(3).and_then(arg_expr).and_then(as_string_lit) + && let Some(attr) = attr { - assertions.push(ResponseAssertion { - kind: AssertionKind::Attr, - name: Some(attr.to_string()), - value: Some(value.to_string()), - }); + match (&literal_value, &reference_path) { + (Some(value), _) => assertions.push(ResponseAssertion { + kind: AssertionKind::Attr, + name: Some(attr.to_string()), + value: Some(value.clone()), + reference_path: None, + }), + (None, Some(path)) => assertions.push(ResponseAssertion { + kind: AssertionKind::Reference, + name: Some(attr.to_string()), + value: None, + reference_path: Some(path.clone()), + }), + (None, None) => { + resolver.drop_note("literal attr value not statically resolvable") + } + } } let inner = args .first() @@ -473,6 +583,9 @@ fn classify_call( int_range: None, wire_name, source_path, + literal_value, + enum_ref: None, + reference_path: None, }, None => Binding::None, } @@ -489,6 +602,7 @@ fn classify_call( kind: AssertionKind::Content, name: None, value: Some(value.to_string()), + reference_path: None, }); } Binding::None @@ -504,18 +618,46 @@ fn classify_call( int_range: None, wire_name: None, source_path, + literal_value: None, + enum_ref: None, + reference_path: None, }, - // Optional literal → a present-or-absent marker; treat as optional string. - "optionalLiteral" => Binding::Field { - method: wap::MAYBE_ATTR_STRING.to_string(), - field_type: ParsedFieldType::String, - required: false, - byte_length: None, - byte_range: None, - int_range: None, - wire_name, - source_path, - }, + // `optionalLiteral(ACC, node, "attr", "value")` pins the attr *when present* — + // a present-or-absent marker, so it is NOT a variant discriminator (absence + // satisfies every sibling) and deliberately records no assertion. The pinned + // value still rides on the field: an emitter may omit `type` on a successful + // promote, but if it sends one it must be `"admin"` — sending a status code + // there is exactly the bug this carries the value to prevent. + "optionalLiteral" => { + let literal_value = args.get(3).and_then(arg_expr).and_then(static_literal); + let reference_path = args + .get(3) + .and_then(arg_expr) + .and_then(|e| reference_path_of(e, bindings)); + if literal_value.is_none() && reference_path.is_none() { + resolver.drop_note("optionalLiteral attr value not statically resolvable"); + } + Binding::Field { + method: wap::MAYBE_ATTR_STRING.to_string(), + field_type: ParsedFieldType::String, + required: false, + byte_length: None, + byte_range: None, + int_range: None, + wire_name, + source_path, + literal_value, + enum_ref: None, + reference_path, + } + } + // The `WASmaxParseReference` helpers read a value off the REQUEST, not the + // response node — the source of the `from`/`id` echo rules. + "attrStringFromReference" + | "optionalAttrStringFromReference" + | "attrFromReference" + | "optionalAttrFromReference" + | "contentStringFromReference" => classify_reference(method, args, resolver), // `optional(ACCESSOR, node, …)` → the wrapped accessor decides the type; // required = false. "optional" => { @@ -536,6 +678,9 @@ fn classify_call( int_range, wire_name, source_path, + literal_value: None, + enum_ref: enum_arg_ref(inner, args, resolver), + reference_path: None, } } None => Binding::None, @@ -564,6 +709,9 @@ fn classify_call( int_range, wire_name, source_path, + literal_value: None, + enum_ref: enum_arg_ref(Some(other), args, resolver), + reference_path: None, } } None => Binding::None, @@ -571,6 +719,128 @@ fn classify_call( } } +/// A compile-time literal pinned as an assertion's expected value, stringified: +/// `"result"` → `"result"`, `429` → `"429"`, `!0`/`true` → `"true"`. The field's +/// declared type says how to read it back, so one string form covers every accessor +/// (an `attrInt` field's `"429"` is the integer 429 on the wire). +fn static_literal(e: &Expression) -> Option { + if let Some(s) = as_string_lit(e) { + return Some(s.to_string()); + } + if let Some(n) = as_int(e) { + return Some(n.to_string()); + } + match e { + Expression::BooleanLiteral(b) => Some(b.value.to_string()), + // The minifier writes booleans as `!0` / `!1`. + Expression::UnaryExpression(u) if u.operator == oxc_ast::ast::UnaryOperator::LogicalNot => { + as_int(&u.argument).map(|n| (n == 0).to_string()) + } + _ => None, + } +} + +/// The request path a `literal(…, V.value)` echo guard compares against, when `V` is +/// bound to a `WASmaxParseReference` helper. `None` for any other value expression — +/// this never infers an echo rule from a name coincidence. +fn reference_path_of(e: &Expression, bindings: &HashMap) -> Option> { + let (var, "value") = value_member(e)? else { + return None; + }; + match bindings.get(var) { + Some(Binding::Reference { path, .. }) => Some(path.clone()), + _ => None, + } +} + +/// Classify a `WASmaxParseReference` helper call into a [`Binding::Reference`]. +/// +/// The two shapes differ only in whether the accessor is passed explicitly: +/// `attrStringFromReference(request, ["to"])` — implicitly attrString +/// `attrFromReference(o("WASmaxParseJid").attrJidEnum, request, ["from"], ENUM)` +/// +/// The path argument is the helper's own contract: every element but the last is a +/// child tag to descend in the request, the last is the attribute read there. +fn classify_reference(method: &str, args: &[Argument], resolver: &Resolver) -> Binding { + let Some(path) = args.iter().find_map(|a| arg_expr(a).and_then(string_array)) else { + resolver.drop_note("reference path argument not statically resolvable"); + return Binding::None; + }; + // An explicit accessor (the `attrFromReference` family) decides the type; the + // `attrString…` spelling is fixed to a string. + let (base_method, field_type) = match method { + "contentStringFromReference" => (wap::CONTENT_STRING.to_string(), ParsedFieldType::String), + "attrStringFromReference" | "optionalAttrStringFromReference" => { + (wap::ATTR_STRING.to_string(), ParsedFieldType::String) + } + _ => args + .first() + .and_then(arg_expr) + .and_then(inner_accessor_name) + .and_then(normalize_accessor) + .map(|(m, ft, _)| (m, ft)) + .unwrap_or((wap::ATTR_STRING.to_string(), ParsedFieldType::String)), + }; + let optional = method.starts_with("optional"); + Binding::Reference { + path, + method: if optional { + optional_variant(&base_method) + } else { + base_method + }, + field_type, + required: !optional, + } +} + +/// An array literal of string literals (`["account","action"]`), or `None` if any +/// element isn't one. +fn string_array(e: &Expression) -> Option> { + let Expression::ArrayExpression(arr) = e else { + return None; + }; + arr.elements + .iter() + .map(|el| { + el.as_expression() + .and_then(as_string_lit) + .map(str::to_string) + }) + .collect::>>() + .filter(|v| !v.is_empty()) +} + +/// The wire enum an enum accessor validates against: the `o("Mod").ENUM_NAME` argument +/// of `attrStringEnum(node, "state", ENUM)` / `contentStringEnum(node, ENUM)`, resolved +/// to its variants. `None` for a non-enum accessor. +fn enum_arg_ref( + accessor: Option<&str>, + args: &[Argument], + resolver: &Resolver, +) -> Option { + if !matches!(accessor, Some("attrStringEnum") | Some("contentStringEnum")) { + return None; + } + // The enum is the only `o("Mod").NAME` member *reference* among the args (the node + // is an identifier / `X.value`, the attr a string, and — in the `optional(ACC, …)` + // form — the leading accessor ref is itself `o("WASmaxParseUtils").attrStringEnum`, + // excluded by requiring a non-`WASmaxParse*` owner module). + args.iter() + .filter_map(arg_expr) + .find_map(module_member_ref) + .and_then(|(module, name)| resolver.resolve_enum(&module, &name)) +} + +/// `o("Mod").NAME` (a member reference, not a call) → `(Mod, NAME)`, excluding the +/// `WASmaxParse*` helper namespaces (those are accessor references, not enums). +fn module_member_ref(e: &Expression) -> Option<(String, String)> { + let (obj, prop) = wa_oxc::as_member(e)?; + let inner = as_call(obj)?; + let owner = as_string_lit(arg_expr(inner.arguments.first()?)?)?; + (!owner.starts_with("WASmaxParse")).then(|| (owner.to_string(), prop.to_string())) +} + /// The wrapper tags descended to reach an accessor's node argument: the full /// [`Binding::ChildNode`] path when the node is `n.value` (a `flattenedChildWithTag` /// descent), or empty when the node is the parent param (no descent). The node is @@ -1073,6 +1343,9 @@ fn collect_object_fields( int_range, wire_name, source_path, + literal_value, + enum_ref, + reference_path, }) => { let (int_min, int_max) = match int_range { Some((min, max)) => (Some(*min), Some(*max)), @@ -1094,6 +1367,27 @@ fn collect_object_fields( int_min, int_max, source_path: source_path.clone(), + literal_value: literal_value.clone(), + enum_ref: enum_ref.clone(), + reference_path: reference_path.clone(), + ..Default::default() + }); + } + // A value the parser lifts from the REQUEST and the tail names as an output + // field. Its wire name is the last path element (the attribute read there); + // the wrappers above it, when any, are the request-side descent. + Some(Binding::Reference { + path, + method, + field_type, + required, + }) => { + out.push(ParsedField { + method: method.clone(), + name: key.to_string(), + wire_name: path.last().cloned(), + field_type: *field_type, + required: *required && !optional, ..Default::default() }); } @@ -1619,6 +1913,205 @@ mod tests { assert!(names.contains(&"type"), "mixin fields spread in"); } + #[test] + fn reference_echo_becomes_a_reference_assertion() { + // The rule 17 of the 26 namespace error mixins enforce (and every success + // parser): `from` must equal the REQUEST's `to`, `id` the request's `id`. Both + // must land as `reference` assertions carrying where the value comes from — + // an emitter that hardcodes `from="s.whatsapp.net"` breaks every `g.us` answer. + let body = r#"function e(node, ref){ + var n = o("WASmaxParseUtils").assertTag(node, "iq"); if(!n.success) return n; + var r = o("WASmaxParseReference").attrStringFromReference(ref, ["id"]); if(!r.success) return r; + var a = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "id", r.value); if(!a.success) return a; + var i = o("WASmaxParseReference").attrStringFromReference(ref, ["to"]); if(!i.success) return i; + var l = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "from", i.value); if(!l.success) return l; + var s = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "type", "error"); + return s.success ? o("WAResultOrError").makeResult({ type: s.value }) : s; + }"#; + let (asserts, fields) = analyze_one(body).expect("analyzed"); + let by = |name: &str| { + asserts + .iter() + .find(|a| a.name.as_deref() == Some(name)) + .unwrap_or_else(|| panic!("no assertion for {name}")) + }; + assert_eq!(by("from").kind, AssertionKind::Reference); + assert_eq!( + by("from").reference_path.as_deref(), + Some(&["to".to_string()][..]) + ); + assert_eq!(by("from").value, None, "a reference has no constant value"); + assert_eq!(by("id").kind, AssertionKind::Reference); + assert_eq!( + by("id").reference_path.as_deref(), + Some(&["id".to_string()][..]) + ); + // A constant literal is still a plain attr assertion, with its value. + assert_eq!(by("type").kind, AssertionKind::Attr); + assert_eq!(by("type").value.as_deref(), Some("error")); + // …and it is also the `type` output field, now carrying its pinned value. + let ty = fields + .iter() + .find(|f| f.name == "type") + .expect("type field"); + assert_eq!(ty.literal_value.as_deref(), Some("error")); + } + + #[test] + fn multi_hop_reference_path_is_kept_whole() { + // `attrStringFromReference(request, ["account","action"])` reads the `action` + // attr of the request's `` CHILD — the descent must survive. + let body = r#"function e(node, ref){ + var r = o("WASmaxParseReference").attrStringFromReference(ref, ["account","action"]); if(!r.success) return r; + var a = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "action", r.value); + return o("WAResultOrError").makeResult({ action: a.value }); + }"#; + let (asserts, _f) = analyze_one(body).expect("analyzed"); + let a = asserts + .iter() + .find(|a| a.kind == AssertionKind::Reference) + .expect("reference assertion"); + assert_eq!( + a.reference_path.as_deref(), + Some(&["account".to_string(), "action".to_string()][..]) + ); + } + + #[test] + fn numeric_literal_pins_both_the_assertion_and_the_field() { + // The error mixins pin `code` with an INT literal; dropping it would leave the + // per-RPC error vocabulary with texts but no codes. + let body = r#"function e(node){ + var t = o("WASmaxParseUtils").assertTag(node, "error"); if(!t.success) return t; + var n = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "text", "rate-overlimit"); if(!n.success) return n; + var r = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrInt, node, "code", 429); + return r.success ? o("WAResultOrError").makeResult({ text: n.value, code: r.value }) : r; + }"#; + let (asserts, fields) = analyze_one(body).expect("analyzed"); + let code_assert = asserts + .iter() + .find(|a| a.name.as_deref() == Some("code")) + .expect("code assertion"); + assert_eq!(code_assert.value.as_deref(), Some("429")); + let code = fields + .iter() + .find(|f| f.name == "code") + .expect("code field"); + assert_eq!(code.literal_value.as_deref(), Some("429")); + assert_eq!(code.field_type, ParsedFieldType::Integer); + } + + #[test] + fn optional_literal_pins_a_value_without_asserting_it() { + // `optionalLiteral(attrString, participant, "type", "admin")` — a successful + // promote answers ``. The value must ride on the + // field (barback sent a status code there), but it must NOT become an + // assertion: absence is legal, so it discriminates nothing. + let body = r#"function e(node){ + var s = o("WASmaxParseUtils").optionalLiteral(o("WASmaxParseUtils").attrString, node, "type", "admin"); + return o("WAResultOrError").makeResult({ type: s.value }); + }"#; + let (asserts, fields) = analyze_one(body).expect("analyzed"); + assert!( + !asserts.iter().any(|a| a.name.as_deref() == Some("type")), + "an optional literal is not a discriminator" + ); + let ty = fields + .iter() + .find(|f| f.name == "type") + .expect("type field"); + assert_eq!(ty.literal_value.as_deref(), Some("admin")); + assert!(!ty.required, "optionalLiteral → the attr may be absent"); + } + + #[test] + fn optional_literal_can_pin_to_a_request_value() { + // `optionalLiteral(attrString, list, "c_dhash", ref.item.dhash)` — an echo that + // is NOT a guard (the attr may be absent), so it rides on the field rather than + // becoming an assertion. Without it the rule would only be countable as a drop. + let body = r#"function e(node, ref){ + var m = o("WASmaxParseReference").optionalAttrStringFromReference(ref, ["item","dhash"]); + var s = o("WASmaxParseUtils").optionalLiteral(o("WASmaxParseUtils").attrString, node, "c_dhash", m.value); + return o("WAResultOrError").makeResult({ cDhash: s.value }); + }"#; + let (asserts, fields) = analyze_one(body).expect("analyzed"); + assert!( + !asserts.iter().any(|a| a.kind == AssertionKind::Reference), + "an optional echo guards nothing, so it is not an assertion" + ); + let f = fields.iter().find(|f| f.name == "cDhash").expect("field"); + assert_eq!( + f.reference_path.as_deref(), + Some(&["item".to_string(), "dhash".to_string()][..]) + ); + assert_eq!(f.literal_value, None, "an echo is not a constant"); + assert!(!f.required); + } + + #[test] + fn enum_accessor_resolves_its_variants() { + // A response enum field must carry the legal values, not just `type: "enum"`. + let enums = r#"__d("WASmaxInFooEnums",[],(function(t,n,r,o,a,i){ + var e={off:"off",on:"on"}; i.ENUM_OFF_ON=e; + }),66);"#; + let mut slices = HashMap::new(); + slices.insert("WASmaxInFooEnums", enums); + let resolver = Resolver::new(&slices); + let body = r#"function e(node){ + var s = o("WASmaxParseUtils").attrStringEnum(node, "state", o("WASmaxInFooEnums").ENUM_OFF_ON); if(!s.success) return s; + var t = o("WASmaxParseUtils").optional(o("WASmaxParseUtils").attrStringEnum, node, "mode", o("WASmaxInFooEnums").ENUM_OFF_ON); + return o("WAResultOrError").makeResult({ state: s.value, mode: t.value }); + }"#; + let (_a, fields) = + analyze_fn_source(body, &LocalFns::new(), &resolver, &mut HashSet::new()) + .expect("analyzed"); + for name in ["state", "mode"] { + let f = fields.iter().find(|f| f.name == name).expect(name); + let er = f.enum_ref.as_ref().unwrap_or_else(|| panic!("{name} enum")); + assert_eq!(er.name, "ENUM_OFF_ON"); + assert_eq!(er.module, "WASmaxInFooEnums"); + let values: Vec<&str> = er.variants.iter().map(|v| v.value.as_str()).collect(); + assert_eq!(values, ["off", "on"]); + } + } + + #[test] + fn unresolvable_enum_is_recorded_not_guessed() { + // The enum module isn't in the bundle: no link, and a drop reason so a consumer + // can tell "no enum here" from "there was one and we lost it". + let slices = HashMap::new(); + let resolver = Resolver::new(&slices); + let body = r#"function e(node){ + var s = o("WASmaxParseUtils").attrStringEnum(node, "state", o("Missing").ENUM_OFF_ON); if(!s.success) return s; + return o("WAResultOrError").makeResult({ state: s.value }); + }"#; + let (_a, fields) = + analyze_fn_source(body, &LocalFns::new(), &resolver, &mut HashSet::new()) + .expect("analyzed"); + assert!(fields[0].enum_ref.is_none()); + assert_eq!( + resolver + .drop_counts() + .get("response enum argument not structurally resolvable"), + Some(&1) + ); + } + + #[test] + fn reference_value_named_in_make_result_is_still_a_field() { + // A reference binding usually only feeds an echo guard, but when the tail names + // it, it is a real output field — it must not vanish just because it reads off + // the request. + let body = r#"function e(node, ref){ + var r = o("WASmaxParseReference").attrStringFromReference(ref, ["id"]); if(!r.success) return r; + return o("WAResultOrError").makeResult({ id: r.value }); + }"#; + let (_a, fields) = analyze_one(body).expect("analyzed"); + let id = fields.iter().find(|f| f.name == "id").expect("id field"); + assert_eq!(id.wire_name.as_deref(), Some("id")); + assert!(id.required); + } + #[test] fn unrecognized_tail_yields_none() { let body = r#"function e(node){ return somethingElse(node); }"#; diff --git a/crates/wa-scan/tests/iq_roundtrip.rs b/crates/wa-scan/tests/iq_roundtrip.rs new file mode 100644 index 0000000..cb05f54 --- /dev/null +++ b/crates/wa-scan/tests/iq_roundtrip.rs @@ -0,0 +1,278 @@ +//! Guard: the committed IQ IR carries enough to **emit** a stanza, not only to read one. +//! +//! The IR has always described what fields a stanza has. That is enough to parse a +//! well-formed response and not enough to produce one — which is how a mock server can +//! answer a successful `promote` with `` (a status code where +//! the role goes) and have a real client reject the whole thing, or answer an `` +//! addressed to `g.us` with `from="s.whatsapp.net"` and be unparseable. +//! +//! So this test closes the loop the way a consumer would: for every `` success +//! variant, build a stanza from **the IR alone** — nothing but the recorded assertions +//! and pinned field values — and check the recorded assertions accept it. A constraint +//! that the IR fails to carry shows up here as an emitted stanza that the same IR then +//! rejects. +//! +//! Both halves are deliberately naive: the emitter only knows how to satisfy +//! constraints, and the checker only knows how to verify them. Neither shares code with +//! the extractor, so a mis-extraction cannot cancel itself out. + +use std::collections::BTreeMap; +use std::path::Path; + +use wa_ir::{AssertionKind, IqIr, ParsedField, ResponseAssertion, ResponseVariantKind}; + +/// A minimal stanza: a tag, attributes, and text content. Everything the recorded +/// assertions can talk about. +#[derive(Debug, Default)] +struct Node { + tag: String, + attrs: BTreeMap, + content: Option, +} + +/// The request a response is answering, as far as the echo rules care: the attribute +/// values a `reference` assertion can point at. Keyed by the joined `referencePath`, so +/// a multi-hop path (`["account","action"]`) is addressable too. +fn sample_request() -> BTreeMap { + // A group-addressed request — the case that broke: `to` is NOT `s.whatsapp.net`, so + // an emitter that hardcodes the server JID fails the `from` echo. + BTreeMap::from([ + ("id".to_string(), "1234.5678-9".to_string()), + ("to".to_string(), "120363000000000000@g.us".to_string()), + ("account/action".to_string(), "sync".to_string()), + ]) +} + +/// Build the response node a variant describes, using only the IR. +fn emit(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> Node { + let request = sample_request(); + let mut node = Node::default(); + for a in assertions { + match a.kind { + AssertionKind::Tag => node.tag = a.name.clone().unwrap_or_default(), + AssertionKind::Attr => { + if let (Some(name), Some(value)) = (&a.name, &a.value) { + node.attrs.insert(name.clone(), value.clone()); + } + } + AssertionKind::Content => node.content = a.value.clone(), + // The whole point of the `reference` kind: the emitter reads the expected + // value out of the REQUEST rather than inventing one. + AssertionKind::Reference => { + if let (Some(name), Some(path)) = (&a.name, &a.reference_path) + && let Some(value) = request.get(&path.join("/")) + { + node.attrs.insert(name.clone(), value.clone()); + } + } + AssertionKind::FromServer => {} + } + } + // Pinned field values (`type="admin"`, `matched="true"`) are constraints too: a + // required one must be emitted, and an optional one must not be contradicted. A + // field-level `referencePath` is the same rule with the value taken from the + // request (the optional twin of a `reference` assertion). + for f in fields { + if f.source_path.is_some() || f.same_node { + continue; // not read off this node + } + let wire = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); + if let Some(value) = pinned_value(f, &request) + && f.required + { + node.attrs.entry(wire).or_insert(value); + } + } + node +} + +/// The value a field is pinned to, if any: a constant, or the request value it echoes. +fn pinned_value(f: &ParsedField, request: &BTreeMap) -> Option { + if let Some(v) = &f.literal_value { + return Some(v.clone()); + } + let path = f.reference_path.as_ref()?; + request.get(&path.join("/")).cloned() +} + +/// Check the node against the same assertions, reporting each unsatisfied one. +fn violations( + node: &Node, + assertions: &[ResponseAssertion], + fields: &[ParsedField], +) -> Vec { + let request = sample_request(); + let mut out = Vec::new(); + for a in assertions { + match a.kind { + AssertionKind::Tag => { + if let Some(tag) = &a.name + && &node.tag != tag + { + out.push(format!("tag: expected <{tag}>, got <{}>", node.tag)); + } + } + AssertionKind::Attr => { + if let (Some(name), Some(value)) = (&a.name, &a.value) + && node.attrs.get(name) != Some(value) + { + out.push(format!( + "attr {name}: expected {value:?}, got {:?}", + node.attrs.get(name) + )); + } + } + AssertionKind::Content => { + if node.content.as_ref() != a.value.as_ref() { + out.push(format!( + "content: expected {:?}, got {:?}", + a.value, node.content + )); + } + } + AssertionKind::Reference => { + let Some(name) = &a.name else { continue }; + let Some(path) = &a.reference_path else { + out.push(format!("reference {name}: no referencePath recorded")); + continue; + }; + let Some(expected) = request.get(&path.join("/")) else { + // The path names a request field the sample doesn't model. Not a + // failure of the IR — but flag an unrecognised shape rather than + // passing silently. + out.push(format!( + "reference {name}: unmodelled request path {path:?}" + )); + continue; + }; + if node.attrs.get(name) != Some(expected) { + out.push(format!( + "reference {name}: expected the request's {path:?} ({expected:?}), got {:?}", + node.attrs.get(name) + )); + } + } + AssertionKind::FromServer => {} + } + } + for f in fields { + if f.source_path.is_some() || f.same_node { + continue; // not read off this node + } + // A field pinned to a request path the sample doesn't model can't be checked, + // but an unrecognised shape must still be visible rather than pass silently. + if f.literal_value.is_none() + && let Some(path) = &f.reference_path + && !request.contains_key(&path.join("/")) + { + out.push(format!( + "field {}: unmodelled request path {path:?}", + f.name + )); + continue; + } + let Some(value) = pinned_value(f, &request) else { + continue; + }; + let wire = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); + match (node.attrs.get(&wire), f.required) { + // A required pin must be present and exact. + (None, true) => out.push(format!("pinned {wire}: required {value:?} not emitted")), + (Some(got), _) if *got != value => out.push(format!( + "pinned {wire}: expected {value:?}, emitted {got:?}" + )), + // An optional pin may be absent; it just must not contradict. + _ => {} + } + } + out +} + +#[test] +fn every_iq_success_variant_round_trips_through_its_own_constraints() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../generated/iq/index.json"); + // Committed, so CI always has it: a missing file there means the guard is silently + // not running. Locally (sparse checkout) skip instead of failing spuriously — + // mirroring `wa-proto`'s committed-artifact guard. + if !path.exists() { + assert!( + std::env::var_os("CI").is_none(), + "{} is absent under CI — the IQ round-trip guard would be silently skipped", + path.display() + ); + eprintln!("skipping: {} not present (local only)", path.display()); + return; + } + let raw = std::fs::read_to_string(&path).expect("read generated/iq/index.json"); + let ir: IqIr = serde_json::from_str(&raw).expect("parse the committed IQ IR"); + + let mut checked = 0usize; + let mut failures = Vec::new(); + for stanza in &ir.stanzas { + // Both response shapes: a single-shape response's own assertions, and each + // success variant of an outcome union. + let shapes = std::iter::once(( + stanza.response.parser_name.clone(), + &stanza.response.assertions, + &stanza.response.fields, + )) + .chain( + stanza + .response + .variants + .iter() + .filter(|v| v.kind == ResponseVariantKind::Success) + .map(|v| (v.tag.clone(), &v.assertions, &v.fields)), + ); + for (name, assertions, fields) in shapes { + if assertions.is_empty() { + continue; // nothing recorded to satisfy or to check + } + checked += 1; + let node = emit(assertions, fields); + for v in violations(&node, assertions, fields) { + failures.push(format!("{} / {name}: {v}", stanza.module_name)); + } + } + } + + assert!( + checked > 0, + "no IQ response shape carried any assertion — the constraint layer is empty" + ); + assert!( + failures.is_empty(), + "{} of {checked} IR-built response shape(s) fail their own recorded constraints:\n {}", + failures.len(), + failures.join("\n ") + ); + eprintln!("round-tripped {checked} IQ response shape(s) from the IR alone"); +} + +#[test] +fn a_hardcoded_from_fails_the_echo_rule() { + // The negative control: without this the round-trip could pass by vacuously + // ignoring `reference` assertions. Mirrors barback's real bug — answering a request + // addressed to `g.us` with `from="s.whatsapp.net"`. + let assertions = vec![ + ResponseAssertion { + kind: AssertionKind::Tag, + name: Some("iq".into()), + value: None, + reference_path: None, + }, + ResponseAssertion { + kind: AssertionKind::Reference, + name: Some("from".into()), + value: None, + reference_path: Some(vec!["to".into()]), + }, + ]; + let mut node = emit(&assertions, &[]); + assert!(violations(&node, &assertions, &[]).is_empty()); + node.attrs + .insert("from".into(), "s.whatsapp.net".to_string()); + let broken = violations(&node, &assertions, &[]); + assert_eq!(broken.len(), 1, "{broken:?}"); + assert!(broken[0].contains("reference from"), "{broken:?}"); +} diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index ee9b7b4..8a741f0 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -748,6 +748,15 @@ struct Counts { /// `manifest.diagnostics.iq.{stanzas,typedResponses}`. iq_stanzas: usize, iq_typed_responses: usize, + /// Validation-constraint coverage, mirroring + /// `manifest.diagnostics.iq.constraints` — see [`IqConstraintCounts`]. + iq_reference_constraints: usize, + iq_field_literals: usize, + iq_field_enum_refs: usize, + iq_typed_error_variants: usize, + iq_error_texts: usize, + /// Notification payload action-union arms recovered (`diagnostics.notif.actions`). + notif_actions: usize, /// Outgoing non-IQ stanzas (receipt/presence/chatstate/ack) the scanner recovers. stanza_defs: usize, /// Incoming content-stanza read-shapes (message/receipt/call/ack) recovered from @@ -790,6 +799,75 @@ struct IqDiagnostics { /// fields recovered. `requests_enriched`/`fields_recovered` going to 0 flags a /// regression in Phase-2 fragment merging. cross_module: wa_scan::CrossModuleStats, + /// Validation-constraint coverage — see [`IqConstraintCounts`]. + constraints: IqConstraintCounts, +} + +/// How many *validation constraints* the IQ IR carries, counted over the emitted +/// document. These are the numbers the floor guard watches for the constraint layer: +/// each one keys on a distinct JS construct, so a WA refactor that hides one fails +/// loudly instead of silently emptying a field that consumers depend on. +#[derive(Debug, Default, Clone, Copy)] +struct IqConstraintCounts { + /// Echo rules — "this attribute must carry a value taken from the request" — in + /// both forms: `reference` assertions (the required `from` == the request's `to`) + /// and field-level `referencePath` pins (the optional ones). + reference_constraints: usize, + /// `ParsedField`s carrying a pinned `literalValue`. + field_literals: usize, + /// Response fields whose enum argument resolved to a real variant set. + field_enum_refs: usize, + /// Response variants classified as a client or server error. + typed_error_variants: usize, + /// Distinct `` values across every RPC's error vocabulary. + error_texts: usize, +} + +/// Count the validation constraints in an emitted IQ IR (see [`IqConstraintCounts`]). +fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { + fn walk_fields(fields: &[wa_ir::ParsedField], c: &mut IqConstraintCounts) { + for f in fields { + if f.literal_value.is_some() { + c.field_literals += 1; + } + if f.reference_path.is_some() { + c.reference_constraints += 1; + } + if f.enum_ref.is_some() { + c.field_enum_refs += 1; + } + if let Some(children) = &f.children { + walk_fields(children, c); + } + for uv in f.union_variants.iter().flatten() { + c.reference_constraints += count_references(&uv.assertions); + walk_fields(&uv.fields, c); + } + } + } + fn count_references(assertions: &[wa_ir::ResponseAssertion]) -> usize { + assertions + .iter() + .filter(|a| a.kind == wa_ir::AssertionKind::Reference) + .count() + } + + let mut c = IqConstraintCounts::default(); + let mut texts = std::collections::BTreeSet::new(); + for s in &ir.stanzas { + c.reference_constraints += count_references(&s.response.assertions); + walk_fields(&s.response.fields, &mut c); + for v in &s.response.variants { + c.reference_constraints += count_references(&v.assertions); + if v.error_class.is_some() { + c.typed_error_variants += 1; + } + texts.extend(v.error_texts.iter().cloned()); + walk_fields(&v.fields, &mut c); + } + } + c.error_texts = texts.len(); + c } /// A loaded bundle set: the stamped version, the concatenated source the extractors @@ -1532,7 +1610,7 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou let (abprops_arts, abprops_count) = abprops.expect(checked); let (enums_arts, enums_count) = enums.expect(checked); let (wam_arts, wam_count) = wam.expect(checked); - let (notif_arts, (notif_count, notif_typed, notif_tags)) = notif.expect(checked); + let (notif_arts, (notif_count, notif_typed, notif_tags, notif_actions)) = notif.expect(checked); let (stanza_arts, stanza_count) = stanza.expect(checked); let (tokens_arts, (token_single, token_double)) = tokens.expect(checked); let (incoming_arts, incoming_count) = incoming.expect(checked); @@ -1601,6 +1679,12 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou notif_stanza_tags: notif_tags, iq_stanzas: iq_diag.stanzas, iq_typed_responses: iq_diag.typed_responses, + iq_reference_constraints: iq_diag.constraints.reference_constraints, + iq_field_literals: iq_diag.constraints.field_literals, + iq_field_enum_refs: iq_diag.constraints.field_enum_refs, + iq_typed_error_variants: iq_diag.constraints.typed_error_variants, + iq_error_texts: iq_diag.constraints.error_texts, + notif_actions, stanza_defs: stanza_count, incoming_defs: incoming_count, server_request_defs: srvreq_count, @@ -1701,12 +1785,20 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou "requestsEnriched": iq_diag.cross_module.requests_enriched, "fieldsRecovered": iq_diag.cross_module.fields_recovered, }, + "constraints": { + "referenceConstraints": iq_diag.constraints.reference_constraints, + "fieldLiterals": iq_diag.constraints.field_literals, + "fieldEnumRefs": iq_diag.constraints.field_enum_refs, + "typedErrorVariants": iq_diag.constraints.typed_error_variants, + "errorTexts": iq_diag.constraints.error_texts, + }, }, "notif": { "types": counts.notif_types, "typedContent": counts.notif_typed_content, "degraded": counts.notif_types - counts.notif_typed_content, "stanzaTags": counts.notif_stanza_tags, + "actions": counts.notif_actions, }, }, }); @@ -1776,6 +1868,25 @@ fn check_floor(out: &Path, counts: &Counts) -> Result> { regressions.push(format!("iq.{key}: {prev} → {new}")); } } + // The validation-constraint layer. Each counter keys on a distinct JS construct + // (`attrStringFromReference`, `literal`/`optionalLiteral`, the enum accessors, + // the error mixins), so a WA refactor that hides one shows up here rather than + // as a silently emptier field in every consumer's copy of the IR. + if let Some(c) = iq.get("constraints") { + for (key, new) in [ + ("referenceConstraints", counts.iq_reference_constraints), + ("fieldLiterals", counts.iq_field_literals), + ("fieldEnumRefs", counts.iq_field_enum_refs), + ("typedErrorVariants", counts.iq_typed_error_variants), + ("errorTexts", counts.iq_error_texts), + ] { + if let Some(prev) = c.get(key).and_then(serde_json::Value::as_u64) + && (new as u64) < prev + { + regressions.push(format!("iq.constraints.{key}: {prev} → {new}")); + } + } + } } // Notification coverage below the catalog count: a drop in typed-content means a // handler's parser stopped resolving; a drop in stanzaTags means the tag-switch @@ -1784,6 +1895,7 @@ fn check_floor(out: &Path, counts: &Counts) -> Result> { for (key, new) in [ ("typedContent", counts.notif_typed_content), ("stanzaTags", counts.notif_stanza_tags), + ("actions", counts.notif_actions), ] { if let Some(prev) = notif.get(key).and_then(serde_json::Value::as_u64) && (new as u64) < prev @@ -1927,8 +2039,9 @@ fn push_iq( source: &str, module_defs: &[wa_transform::ModuleDefinition], ) -> Result<(usize, IqDiagnostics)> { - let (ir, cross_module) = + let (ir, scan_stats) = wa_scan::extract_iq_from_modules_with_diagnostics(source, module_defs, wa_version); + let cross_module = scan_stats.cross_module; // M8/M9 diagnostics. Every IQ candidate module yields ≥1 stanza or exactly one // `unparseable` entry, so the candidate count is the number of distinct @@ -1976,6 +2089,12 @@ fn push_iq( for u in &ir.unparseable { *drops_by_reason.entry(u.reason.clone()).or_default() += 1; } + // Constraints the response analysis saw but could not resolve structurally. Recorded + // here rather than dropped silently: a consumer must be able to tell "this field has + // no pinned value" from "this field has one and we failed to extract it". + for (reason, n) in &scan_stats.constraint_drops { + *drops_by_reason.entry(reason.clone()).or_default() += n; + } eprintln!( "iq: cross-module fragments -> {} request(s) reference mixins, {} enriched, \ {} field(s) recovered", @@ -1992,6 +2111,7 @@ fn push_iq( excluded_fragments: fragments, drops_by_reason, cross_module, + constraints: iq_constraint_counts(&ir), }; // Neutral, language-agnostic IR (the cross-language contract): the same @@ -2121,7 +2241,7 @@ fn push_notif( wa_version: &str, source: &str, module_defs: &[wa_transform::ModuleDefinition], -) -> Result<(usize, usize, usize)> { +) -> Result<(usize, usize, usize, usize)> { let ir = wa_notif::extract_notif_from_modules(source, module_defs, wa_version); let count = ir.notifications.len(); let stanza_tags = ir.stanza_tags.len(); @@ -2130,9 +2250,12 @@ fn push_notif( .iter() .filter(|n| n.content.is_some()) .count(); + // Payload action-union arms (the `w:gp2` group actions and friends) — the layer + // below the envelope, counted so it can't silently empty out. + let actions: usize = ir.notifications.iter().map(|n| n.actions.len()).sum(); eprintln!( "notif: {count} notification types ({typed} with typed content, {} degraded), \ - {} stanza tags (dispatchers: {})", + {actions} payload action(s), {} stanza tags (dispatchers: {})", count - typed, stanza_tags, if ir.dispatcher_modules.is_empty() { @@ -2151,7 +2274,7 @@ fn push_notif( rel_path: PathBuf::from("notif/notif.rs"), content: wa_codegen::generate_notif(&ir), }); - Ok((count, typed, stanza_tags)) + Ok((count, typed, stanza_tags, actions)) } fn push_wam( @@ -2473,6 +2596,7 @@ mod tests { wasm_binaries: 0, wasm_resources: 0, wasm_wasm_handles: 0, + ..Default::default() }; let regressions = check_floor(&dir, &counts).unwrap(); assert_eq!(regressions.len(), 2); diff --git a/generated/abprops/index.json b/generated/abprops/index.json index 21609ec..3bd43c4 100644 --- a/generated/abprops/index.json +++ b/generated/abprops/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "configs": [ { diff --git a/generated/appstate/index.json b/generated/appstate/index.json index 066d2d9..6f217f2 100644 --- a/generated/appstate/index.json +++ b/generated/appstate/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "collections": [ "regular", diff --git a/generated/enums/index.json b/generated/enums/index.json index 8a66ae9..a315522 100644 --- a/generated/enums/index.json +++ b/generated/enums/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "enums": [ { diff --git a/generated/incoming/index.json b/generated/incoming/index.json index 03459cd..eab5961 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "incoming": [ { @@ -720,10 +720,24 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "class", "value": "message" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -739,7 +753,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "message" }, { "method": "attrInt", @@ -759,7 +774,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "1" } ], "sameNode": true @@ -775,7 +791,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -791,7 +808,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "2" } ], "sameNode": true @@ -807,7 +825,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -823,7 +842,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" } ], "sameNode": true @@ -874,6 +894,24 @@ "wireName": "pricing_model", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_CBP_NBP_PMP", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "CBP", + "value": "CBP" + }, + { + "name": "NBP", + "value": "NBP" + }, + { + "name": "PMP", + "value": "PMP" + } + ] + }, "sourcePath": [ "biz" ] @@ -884,6 +922,20 @@ "wireName": "billable", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "biz" ] @@ -914,6 +966,24 @@ "wireName": "pricing_type", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FREECUSTOMERSERVICE_FREEENTRYPOINT_REGULAR", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "free_customer_service", + "value": "free_customer_service" + }, + { + "name": "free_entry_point", + "value": "free_entry_point" + }, + { + "name": "regular", + "value": "regular" + } + ] + }, "sourcePath": [ "biz" ] @@ -930,7 +1000,21 @@ "name": "optimizationGoal", "wireName": "optimization_goal", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DELIVERY_NOOPTIMIZATION", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "delivery", + "value": "delivery" + }, + { + "name": "no_optimization", + "value": "no_optimization" + } + ] + } } ], "repeats": false, @@ -1148,7 +1232,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "message" }, { "method": "attrInt", @@ -1168,7 +1253,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "1" } ], "sameNode": true @@ -1184,7 +1270,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -1200,7 +1287,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "2" } ], "sameNode": true @@ -1216,7 +1304,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -1232,7 +1321,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" } ], "sameNode": true @@ -1283,6 +1373,24 @@ "wireName": "pricing_model", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_CBP_NBP_PMP", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "CBP", + "value": "CBP" + }, + { + "name": "NBP", + "value": "NBP" + }, + { + "name": "PMP", + "value": "PMP" + } + ] + }, "sourcePath": [ "biz" ] @@ -1293,6 +1401,20 @@ "wireName": "billable", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "biz" ] @@ -1323,6 +1445,24 @@ "wireName": "pricing_type", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FREECUSTOMERSERVICE_FREEENTRYPOINT_REGULAR", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "free_customer_service", + "value": "free_customer_service" + }, + { + "name": "free_entry_point", + "value": "free_entry_point" + }, + { + "name": "regular", + "value": "regular" + } + ] + }, "sourcePath": [ "biz" ] @@ -1339,7 +1479,21 @@ "name": "optimizationGoal", "wireName": "optimization_goal", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DELIVERY_NOOPTIMIZATION", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "delivery", + "value": "delivery" + }, + { + "name": "no_optimization", + "value": "no_optimization" + } + ] + } } ], "repeats": false, @@ -1493,10 +1647,24 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "class", "value": "message" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ] }, @@ -1517,7 +1685,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "message" }, { "method": "attrInt", @@ -1537,7 +1706,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "1" } ], "sameNode": true @@ -1553,7 +1723,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -1569,7 +1740,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "2" } ], "sameNode": true @@ -1585,7 +1757,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -1601,7 +1774,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" } ], "sameNode": true @@ -1652,6 +1826,24 @@ "wireName": "pricing_model", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_CBP_NBP_PMP", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "CBP", + "value": "CBP" + }, + { + "name": "NBP", + "value": "NBP" + }, + { + "name": "PMP", + "value": "PMP" + } + ] + }, "sourcePath": [ "biz" ] @@ -1662,6 +1854,20 @@ "wireName": "billable", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "biz" ] @@ -1692,6 +1898,24 @@ "wireName": "pricing_type", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FREECUSTOMERSERVICE_FREEENTRYPOINT_REGULAR", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "free_customer_service", + "value": "free_customer_service" + }, + { + "name": "free_entry_point", + "value": "free_entry_point" + }, + { + "name": "regular", + "value": "regular" + } + ] + }, "sourcePath": [ "biz" ] @@ -1708,7 +1932,21 @@ "name": "optimizationGoal", "wireName": "optimization_goal", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DELIVERY_NOOPTIMIZATION", + "module": "WASmaxInMessagePublishEnums", + "variants": [ + { + "name": "delivery", + "value": "delivery" + }, + { + "name": "no_optimization", + "value": "no_optimization" + } + ] + } } ], "repeats": false, @@ -1880,10 +2118,24 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "class", "value": "message" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ] } @@ -1902,10 +2154,24 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "class", "value": "status" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1921,7 +2187,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "status" }, { "method": "attrInt", @@ -1944,7 +2211,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "1" } ], "assertions": [ @@ -1967,7 +2235,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" } ], "assertions": [ @@ -1990,7 +2259,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "assertions": [ @@ -2054,10 +2324,24 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "class", "value": "status" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -2075,7 +2359,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "status" }, { "method": "attrInt", @@ -2098,7 +2383,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "1" } ], "assertions": [ @@ -2121,7 +2407,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" } ], "assertions": [ @@ -2144,7 +2431,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "assertions": [ @@ -2174,10 +2462,31 @@ "kind": "tag", "name": "ack" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "class", "value": "receipt" + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, + { + "kind": "reference", + "name": "type", + "referencePath": [ + "type" + ] } ], "fields": [ @@ -2186,7 +2495,8 @@ "name": "class", "wireName": "class", "type": "string", - "required": true + "required": true, + "literalValue": "receipt" }, { "method": "maybeAttrInt", @@ -2200,7 +2510,21 @@ "name": "readreceipts", "wireName": "readreceipts", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ALL_NONE", + "module": "WASmaxInReceiptEnums", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "none", + "value": "none" + } + ] + } }, { "method": "", diff --git a/generated/iq/index.json b/generated/iq/index.json index d7c8a1a..616e011 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "stanzas": [ { @@ -52,6 +52,7 @@ "wireName": "protocol", "type": "string", "required": true, + "literalValue": "1", "sourcePath": [ "props" ] @@ -102,6 +103,20 @@ "wireName": "delta_update", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInAbPropsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "props" ] @@ -111,7 +126,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -222,6 +238,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -235,6 +265,7 @@ "wireName": "protocol", "type": "string", "required": true, + "literalValue": "1", "sourcePath": [ "props" ] @@ -285,6 +316,20 @@ "wireName": "delta_update", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInAbPropsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "props" ] @@ -294,7 +339,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -400,11 +446,33 @@ "tag": "GetExperimentConfigResponseErrorNoRetry", "moduleName": "WASmaxInAbPropsGetExperimentConfigResponseErrorNoRetry", "kind": "error", + "errorCodes": [ + 400, + 501 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -417,7 +485,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -433,14 +502,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -452,6 +523,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -463,14 +539,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -482,6 +560,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -501,6 +584,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -520,14 +617,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -537,7 +636,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -627,7 +727,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -720,6 +821,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -772,7 +887,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -860,11 +976,33 @@ "tag": "GetGroupExperimentConfigResponseErrorNoRetry", "moduleName": "WASmaxInAbPropsGetGroupExperimentConfigResponseErrorNoRetry", "kind": "error", + "errorCodes": [ + 400, + 501 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -877,7 +1015,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -893,14 +1032,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -912,6 +1053,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -923,14 +1069,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -942,6 +1090,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -961,6 +1114,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -980,14 +1147,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -997,7 +1166,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -1043,7 +1213,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -1061,6 +1232,7 @@ "wireName": "addressing_mode", "type": "string", "required": false, + "literalValue": "pn", "sourcePath": [ "list" ] @@ -1119,10 +1291,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1131,7 +1317,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -1149,6 +1336,7 @@ "wireName": "addressing_mode", "type": "string", "required": false, + "literalValue": "pn", "sourcePath": [ "list" ] @@ -1207,10 +1395,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1219,7 +1421,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -1237,6 +1440,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "list" ] @@ -1260,7 +1464,8 @@ "name": "active", "wireName": "active", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "maybeAttrString", @@ -1350,7 +1555,8 @@ "name": "unknownIdentifier", "wireName": "unknown_identifier", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "assertions": [ @@ -1380,10 +1586,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1392,7 +1612,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -1410,6 +1631,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "list" ] @@ -1420,6 +1642,7 @@ "wireName": "dirty", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -1443,7 +1666,8 @@ "name": "active", "wireName": "active", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "maybeAttrString", @@ -1533,7 +1757,8 @@ "name": "unknownIdentifier", "wireName": "unknown_identifier", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "assertions": [ @@ -1563,10 +1788,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1575,7 +1814,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -1593,6 +1833,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "list" ] @@ -1616,7 +1857,8 @@ "name": "active", "wireName": "active", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "maybeAttrString", @@ -1706,7 +1948,8 @@ "name": "unknownIdentifier", "wireName": "unknown_identifier", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "assertions": [ @@ -1736,10 +1979,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -1748,7 +2005,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -1756,11 +2014,34 @@ "tag": "GetBlockListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsGetBlockListResponseInvalidRequest", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 429 + ], + "errorTexts": [ + "bad-request", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -1773,7 +2054,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -1789,14 +2071,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -1808,6 +2092,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -1819,14 +2108,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -1838,6 +2129,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -1852,11 +2148,34 @@ "tag": "GetBlockListResponseInternalServerError", "moduleName": "WASmaxInBlocklistsGetBlockListResponseInternalServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 501 + ], + "errorTexts": [ + "feature-not-implemented", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -1869,7 +2188,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -1885,14 +2205,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -1904,6 +2226,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -1915,14 +2242,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -1934,6 +2263,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -2029,7 +2363,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -2037,6 +2372,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -2062,10 +2398,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -2074,7 +2424,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -2082,6 +2433,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -2107,10 +2459,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -2119,7 +2485,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -2127,6 +2494,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "false", "sourcePath": [ "list" ] @@ -2154,6 +2522,7 @@ "wireName": "addressing_mode", "type": "string", "required": false, + "literalValue": "pn", "sourcePath": [ "list" ] @@ -2205,10 +2574,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -2217,7 +2600,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -2225,6 +2609,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "false", "sourcePath": [ "list" ] @@ -2252,6 +2637,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "list" ] @@ -2275,7 +2661,8 @@ "name": "active", "wireName": "active", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "maybeAttrString", @@ -2365,7 +2752,8 @@ "name": "unknownIdentifier", "wireName": "unknown_identifier", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "assertions": [ @@ -2395,10 +2783,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -2407,7 +2809,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -2415,6 +2818,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "false", "sourcePath": [ "list" ] @@ -2442,6 +2846,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "list" ] @@ -2465,7 +2870,8 @@ "name": "active", "wireName": "active", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "maybeAttrString", @@ -2555,7 +2961,8 @@ "name": "unknownIdentifier", "wireName": "unknown_identifier", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "assertions": [ @@ -2580,11 +2987,40 @@ "tag": "UpdateBlockListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseInvalidRequest", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 405, + 406, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "not-acceptable", + "not-allowed", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -2598,6 +3034,20 @@ "wireName": "addressing_mode", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInBlocklistsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + }, "sourcePath": [ "error" ] @@ -2607,7 +3057,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -2623,14 +3074,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -2666,6 +3119,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -2677,14 +3135,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -2696,6 +3156,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -2707,14 +3172,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -2726,6 +3193,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -2737,14 +3209,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -2756,6 +3230,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -2767,14 +3246,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -2786,6 +3267,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] } @@ -2800,11 +3286,34 @@ "tag": "UpdateBlockListResponseServerError", "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 501 + ], + "errorTexts": [ + "feature-not-implemented", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -2817,7 +3326,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -2833,14 +3343,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -2852,6 +3364,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -2863,14 +3380,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -2882,6 +3401,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -2953,6 +3477,7 @@ "wireName": "v", "type": "string", "required": true, + "literalValue": "2", "sourcePath": [ "bot" ] @@ -2984,7 +3509,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -3005,7 +3531,25 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ALL_CATEGORY_FEATURED", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "category", + "value": "category" + }, + { + "name": "featured", + "value": "featured" + } + ] + } }, { "method": "child", @@ -3047,7 +3591,21 @@ "name": "mode", "wireName": "mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DARK_LIGHT", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "dark", + "value": "dark" + }, + { + "name": "light", + "value": "light" + } + ] + } }, { "method": "contentString", @@ -3099,6 +3657,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -3112,6 +3684,7 @@ "wireName": "v", "type": "string", "required": true, + "literalValue": "2", "sourcePath": [ "bot" ] @@ -3143,7 +3716,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -3164,7 +3738,25 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ALL_CATEGORY_FEATURED", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "category", + "value": "category" + }, + { + "name": "featured", + "value": "featured" + } + ] + } }, { "method": "child", @@ -3206,7 +3798,21 @@ "name": "mode", "wireName": "mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DARK_LIGHT", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "dark", + "value": "dark" + }, + { + "name": "light", + "value": "light" + } + ] + } }, { "method": "contentString", @@ -3258,6 +3864,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -3271,6 +3891,7 @@ "wireName": "v", "type": "string", "required": true, + "literalValue": "3", "sourcePath": [ "bot" ] @@ -3290,7 +3911,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -3338,14 +3960,62 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ALL_CATEGORY_FEATURED", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "category", + "value": "category" + }, + { + "name": "featured", + "value": "featured" + } + ] + } }, { "method": "attrEnum", "name": "displayType", "wireName": "display_type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_HIDDEN_HSCROLL_HSCROLLICEBREAKERS_HSCROLLLARGE_HSCROLLSMALL_LISTVIEW", + "module": "WASmaxInBotEnums", + "variants": [ + { + "name": "hidden", + "value": "hidden" + }, + { + "name": "hscroll", + "value": "hscroll" + }, + { + "name": "hscroll_icebreakers", + "value": "hscroll_icebreakers" + }, + { + "name": "hscroll_large", + "value": "hscroll_large" + }, + { + "name": "hscroll_small", + "value": "hscroll_small" + }, + { + "name": "listview", + "value": "listview" + } + ] + } }, { "method": "child", @@ -3397,11 +4067,37 @@ "tag": "BotListResponseError", "moduleName": "WASmaxInBotBotListResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 405, + 500 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "not-allowed" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -3414,7 +4110,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -3430,14 +4127,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -3449,6 +4148,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -3460,14 +4164,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -3479,6 +4185,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -3490,14 +4201,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -3509,6 +4222,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -3520,14 +4238,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -3539,6 +4259,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] } @@ -3984,7 +4709,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -4041,7 +4767,8 @@ "name": "isCloudApi", "wireName": "is_cloud_api", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "contentBytes", @@ -4338,6 +5065,7 @@ "wireName": "code", "type": "integer", "required": true, + "literalValue": "500", "sourcePath": [ "error" ] @@ -4397,11 +5125,30 @@ "tag": "FetchKeyBundlesResponseSuccess", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseSuccess", "kind": "success", + "errorCodes": [ + 500 + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -4414,7 +5161,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -4471,7 +5219,8 @@ "name": "isCloudApi", "wireName": "is_cloud_api", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "contentBytes", @@ -4768,6 +5517,7 @@ "wireName": "code", "type": "integer", "required": true, + "literalValue": "500", "sourcePath": [ "error" ] @@ -4827,11 +5577,36 @@ "tag": "FetchKeyBundlesResponseRequestError", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseRequestError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 406 + ], + "errorTexts": [ + "bad-request", + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -4844,7 +5619,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -4860,14 +5636,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -4879,6 +5657,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -4890,14 +5673,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -4909,6 +5694,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -4950,11 +5740,34 @@ "tag": "FetchKeyBundlesResponseServerError", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -4967,7 +5780,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -4983,14 +5797,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -5002,6 +5818,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -5118,7 +5939,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -5198,7 +6020,8 @@ "name": "isCloudApi", "wireName": "is_cloud_api", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "contentBytes", @@ -5446,6 +6269,7 @@ "wireName": "code", "type": "integer", "required": true, + "literalValue": "500", "sourcePath": [ "error" ] @@ -5505,11 +6329,30 @@ "tag": "FetchMissingPreKeysResponseSuccess", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseSuccess", "kind": "success", + "errorCodes": [ + 500 + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -5522,7 +6365,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -5602,7 +6446,8 @@ "name": "isCloudApi", "wireName": "is_cloud_api", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "contentBytes", @@ -5850,6 +6695,7 @@ "wireName": "code", "type": "integer", "required": true, + "literalValue": "500", "sourcePath": [ "error" ] @@ -5909,11 +6755,36 @@ "tag": "FetchMissingPreKeysResponseRequestError", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseRequestError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 406 + ], + "errorTexts": [ + "bad-request", + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -5926,7 +6797,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -5942,14 +6814,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -5961,6 +6835,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -5972,14 +6851,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -5991,6 +6872,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -6032,11 +6918,34 @@ "tag": "FetchMissingPreKeysResponseServerError", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6049,7 +6958,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -6065,14 +6975,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -6084,6 +6996,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -6299,7 +7216,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -6313,6 +7231,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -6332,6 +7251,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "pq_list" ] @@ -6350,6 +7270,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6362,7 +7296,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -6376,6 +7311,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -6395,6 +7331,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "pq_list" ] @@ -6408,11 +7345,34 @@ "tag": "AddResponseRequestError", "moduleName": "WASmaxInPreKeysAddResponseRequestError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 406 + ], + "errorTexts": [ + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6425,7 +7385,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -6441,14 +7402,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -6484,6 +7447,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -6525,11 +7493,34 @@ "tag": "AddResponseServerError", "moduleName": "WASmaxInPreKeysAddResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6542,7 +7533,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -6558,14 +7550,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -6577,6 +7571,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -6670,7 +7669,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -6683,6 +7683,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6695,7 +7709,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -6703,11 +7718,34 @@ "tag": "DeleteResponseRequestError", "moduleName": "WASmaxInPreKeysDeleteResponseRequestError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 406 + ], + "errorTexts": [ + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6720,7 +7758,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -6736,14 +7775,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -6779,6 +7820,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -6820,11 +7866,34 @@ "tag": "DeleteResponseServerError", "moduleName": "WASmaxInPreKeysDeleteResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -6837,7 +7906,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -6853,14 +7923,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -6872,6 +7944,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -6937,7 +8014,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -6951,6 +8029,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -6970,6 +8049,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "pq_list" ] @@ -6988,6 +8068,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7000,7 +8094,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -7014,6 +8109,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -7033,6 +8129,7 @@ "wireName": "count_low", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "pq_list" ] @@ -7046,11 +8143,30 @@ "tag": "SetResponsePreKeySuccessVnameFailure", "moduleName": "WASmaxInPreKeysSetResponsePreKeySuccessVnameFailure", "kind": "success", + "errorTexts": [ + "bad-request", + "internal-server-error", + "not-acceptable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7074,6 +8190,7 @@ "wireName": "code", "type": "integer", "required": true, + "literalValue": "207", "sourcePath": [ "error" ] @@ -7096,7 +8213,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -7112,7 +8230,8 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrString", @@ -7165,7 +8284,8 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" } ], "assertions": [ @@ -7188,7 +8308,8 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" } ], "assertions": [ @@ -7233,11 +8354,34 @@ "tag": "SetResponseRequestError", "moduleName": "WASmaxInPreKeysSetResponseRequestError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 406 + ], + "errorTexts": [ + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7250,7 +8394,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -7266,14 +8411,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -7309,6 +8456,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -7350,11 +8502,34 @@ "tag": "SetResponseServerError", "moduleName": "WASmaxInPreKeysSetResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7367,7 +8542,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -7383,14 +8559,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -7402,6 +8580,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -7757,7 +8940,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -7770,7 +8954,21 @@ "method": "attrEnum", "name": "elementValue", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_STRONG_WEAK", + "module": "WASmaxInBizCtwaAdAccountEnums", + "variants": [ + { + "name": "Strong", + "value": "Strong" + }, + { + "name": "Weak", + "value": "Weak" + } + ] + } } ], "repeats": false @@ -7786,6 +8984,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7833,7 +9045,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -7846,7 +9059,21 @@ "method": "attrEnum", "name": "elementValue", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_STRONG_WEAK", + "module": "WASmaxInBizCtwaAdAccountEnums", + "variants": [ + { + "name": "Strong", + "value": "Strong" + }, + { + "name": "Weak", + "value": "Weak" + } + ] + } } ], "repeats": false @@ -7862,6 +9089,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7875,6 +9116,7 @@ "wireName": "code", "type": "string", "required": true, + "literalValue": "431", "sourcePath": [ "error" ] @@ -7885,6 +9127,7 @@ "wireName": "text", "type": "string", "required": true, + "literalValue": "TOO_MANY_ATTEMPTS", "sourcePath": [ "error" ] @@ -7901,7 +9144,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -7914,6 +9158,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7927,6 +9185,7 @@ "wireName": "code", "type": "string", "required": true, + "literalValue": "432", "sourcePath": [ "error" ] @@ -7937,6 +9196,7 @@ "wireName": "text", "type": "string", "required": true, + "literalValue": "INCORRECT_NONCE", "sourcePath": [ "error" ] @@ -7953,7 +9213,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -7961,11 +9222,37 @@ "tag": "GetAccessTokenAndSessionCookiesResponseError", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -7985,7 +9272,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -8001,14 +9289,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -8020,6 +9310,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -8031,14 +9326,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -8050,6 +9347,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -8061,14 +9363,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -8080,6 +9384,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -8091,14 +9400,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -8110,6 +9421,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -8179,7 +9495,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -8214,6 +9531,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -8243,7 +9574,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -8273,11 +9605,35 @@ "tag": "GetAccountNonceResponseError", "moduleName": "WASmaxInBizLinkingGetAccountNonceResponseError", "kind": "error", + "errorCodes": [ + 400, + 475, + 500 + ], + "errorTexts": [ + "bad-request", + "internal-server-error", + "notice-required" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -8297,7 +9653,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -8313,14 +9670,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -8332,6 +9691,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -8343,14 +9707,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "notice-required" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "475" }, { "method": "attrInt", @@ -8371,6 +9737,11 @@ "kind": "attr", "name": "text", "value": "notice-required" + }, + { + "kind": "attr", + "name": "code", + "value": "475" } ] }, @@ -8382,14 +9753,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -8401,6 +9774,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -8453,7 +9831,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -8468,6 +9847,20 @@ "wireName": "state", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_OFF_ON", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "off", + "value": "off" + }, + { + "name": "on", + "value": "on" + } + ] + }, "sourcePath": [ "whatsapp_as_page_button" ] @@ -8494,6 +9887,20 @@ "wireName": "has_created_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8504,6 +9911,20 @@ "wireName": "has_active_ctwa_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8520,7 +9941,21 @@ "name": "state", "wireName": "state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DISABLE_IMPORT", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "disable", + "value": "disable" + }, + { + "name": "import", + "value": "import" + } + ] + } } ], "repeats": false @@ -8574,6 +10009,20 @@ "name": "showOnProfileElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "show_on_profile" ] @@ -8622,7 +10071,21 @@ "name": "state", "wireName": "state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DISABLE_IMPORT", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "disable", + "value": "disable" + }, + { + "name": "import", + "value": "import" + } + ] + } }, { "method": "attrString", @@ -8723,6 +10186,20 @@ "name": "showOnProfileElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "show_on_profile" ] @@ -8756,6 +10233,20 @@ "wireName": "has_created_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8766,6 +10257,20 @@ "wireName": "has_active_ctwa_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8787,6 +10292,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -8806,7 +10325,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -8821,6 +10341,20 @@ "wireName": "state", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_OFF_ON", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "off", + "value": "off" + }, + { + "name": "on", + "value": "on" + } + ] + }, "sourcePath": [ "whatsapp_as_page_button" ] @@ -8847,6 +10381,20 @@ "wireName": "has_created_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8857,6 +10405,20 @@ "wireName": "has_active_ctwa_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -8873,7 +10435,21 @@ "name": "state", "wireName": "state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DISABLE_IMPORT", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "disable", + "value": "disable" + }, + { + "name": "import", + "value": "import" + } + ] + } } ], "repeats": false @@ -8927,6 +10503,20 @@ "name": "showOnProfileElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "show_on_profile" ] @@ -8975,7 +10565,21 @@ "name": "state", "wireName": "state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_DISABLE_IMPORT", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "disable", + "value": "disable" + }, + { + "name": "import", + "value": "import" + } + ] + } }, { "method": "attrString", @@ -9076,6 +10680,20 @@ "name": "showOnProfileElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "show_on_profile" ] @@ -9109,6 +10727,20 @@ "wireName": "has_created_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -9119,6 +10751,20 @@ "wireName": "has_active_ctwa_ad", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizLinkingEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "ad_status" ] @@ -9140,6 +10786,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9159,14 +10819,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "repeats": false @@ -9183,7 +10845,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -9196,6 +10859,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9215,14 +10892,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -9239,7 +10918,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -9272,6 +10952,7 @@ "wireName": "status", "type": "string", "required": true, + "literalValue": "Success", "sourcePath": [ "result" ] @@ -9288,7 +10969,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -9301,6 +10983,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9314,6 +11010,7 @@ "wireName": "status", "type": "string", "required": true, + "literalValue": "Success", "sourcePath": [ "result" ] @@ -9330,7 +11027,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -9343,6 +11041,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9356,6 +11068,7 @@ "wireName": "status", "type": "string", "required": true, + "literalValue": "RecoveryRequired", "sourcePath": [ "result" ] @@ -9382,7 +11095,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -9390,11 +11104,37 @@ "tag": "RequestSilentNonceResponseError", "moduleName": "WASmaxInBizAccessTokenRequestSilentNonceResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9414,7 +11154,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -9430,14 +11171,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -9449,6 +11192,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -9460,14 +11208,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -9479,6 +11229,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -9490,14 +11245,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -9509,6 +11266,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -9520,14 +11282,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -9539,6 +11303,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -9584,6 +11353,20 @@ "name": "statusElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizCtwaAdAccountEnums", + "variants": [ + { + "name": "Fail", + "value": "Fail" + }, + { + "name": "Success", + "value": "Success" + } + ] + }, "sourcePath": [ "status" ] @@ -9603,7 +11386,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -9616,6 +11400,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9635,6 +11433,20 @@ "name": "statusElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizCtwaAdAccountEnums", + "variants": [ + { + "name": "Fail", + "value": "Fail" + }, + { + "name": "Success", + "value": "Success" + } + ] + }, "sourcePath": [ "status" ] @@ -9654,7 +11466,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -9662,11 +11475,37 @@ "tag": "SendAccountRecoveryNonceResponseError", "moduleName": "WASmaxInBizCtwaAdAccountSendAccountRecoveryNonceResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -9686,7 +11525,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -9702,14 +11542,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -9721,6 +11563,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -9732,14 +11579,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -9751,6 +11600,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -9762,14 +11616,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -9781,6 +11637,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -9792,14 +11653,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -9811,6 +11674,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -10224,7 +12092,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -10245,7 +12114,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_VIDEO", + "module": "WASmaxInBizCtwaNativeAdEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + } } ], "repeats": false @@ -10269,7 +12152,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_VIDEO", + "module": "WASmaxInBizCtwaNativeAdEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + } } ], "repeats": true @@ -10285,6 +12182,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10304,7 +12215,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -10325,7 +12237,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_VIDEO", + "module": "WASmaxInBizCtwaNativeAdEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + } } ], "repeats": false @@ -10349,7 +12275,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_VIDEO", + "module": "WASmaxInBizCtwaNativeAdEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + } } ], "repeats": true @@ -10360,11 +12300,30 @@ "tag": "UploadAdMediaResponseError", "moduleName": "WASmaxInBizCtwaNativeAdUploadAdMediaResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -10384,7 +12343,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -10400,14 +12360,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -10419,6 +12381,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -10430,14 +12397,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -10449,6 +12418,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -10460,14 +12434,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -10479,6 +12455,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -10490,14 +12471,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -10509,6 +12492,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -10624,7 +12612,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -10637,6 +12626,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10659,7 +12662,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -10667,11 +12671,33 @@ "tag": "GetCountryCodeResponseError", "moduleName": "WASmaxInMdGetCountryCodeResponseError", "kind": "error", + "errorCodes": [ + 400, + 500 + ], + "errorTexts": [ + "bad-request", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10684,7 +12710,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -10700,14 +12727,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -10719,6 +12748,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -10730,14 +12764,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -10749,6 +12785,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -10812,7 +12853,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -10825,6 +12867,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10855,7 +12911,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -10868,6 +12925,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10887,14 +12958,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -10904,7 +12977,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -10952,7 +13026,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -10965,6 +13040,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -10986,7 +13075,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] } @@ -11056,7 +13146,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -11069,6 +13160,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11081,7 +13186,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -11089,11 +13195,33 @@ "tag": "CompanionFinishResponseError", "moduleName": "WASmaxInMdCompanionFinishResponseError", "kind": "error", + "errorCodes": [ + 400, + 500 + ], + "errorTexts": [ + "bad-request", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11106,7 +13234,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -11122,14 +13251,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -11141,6 +13272,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -11152,14 +13288,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -11171,6 +13309,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] } @@ -11272,6 +13415,7 @@ "wireName": "stage", "type": "string", "required": true, + "literalValue": "companion_hello", "sourcePath": [ "link_code_companion_reg" ] @@ -11291,7 +13435,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -11304,6 +13449,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11317,6 +13476,7 @@ "wireName": "stage", "type": "string", "required": true, + "literalValue": "companion_hello", "sourcePath": [ "link_code_companion_reg" ] @@ -11336,7 +13496,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -11344,11 +13505,39 @@ "tag": "CompanionHelloResponseError", "moduleName": "WASmaxInMdCompanionHelloResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 452, + 500 + ], + "errorTexts": [ + "bad-request", + "feature-not-available", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11361,7 +13550,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -11377,14 +13567,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -11396,6 +13588,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -11407,14 +13604,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -11426,6 +13625,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -11437,14 +13641,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-available" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "452" } ], "assertions": [ @@ -11456,6 +13662,11 @@ "kind": "attr", "name": "text", "value": "feature-not-available" + }, + { + "kind": "attr", + "name": "code", + "value": "452" } ] }, @@ -11467,14 +13678,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -11486,6 +13699,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -11497,14 +13715,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -11516,6 +13736,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] } @@ -11561,7 +13786,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -11574,6 +13800,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11586,7 +13826,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] } @@ -11625,7 +13866,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -11638,6 +13880,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11650,7 +13906,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] } @@ -11715,7 +13972,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -11728,6 +13986,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -11740,7 +14012,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] } @@ -11911,7 +14184,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -12046,6 +14320,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -12062,6 +14337,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12101,7 +14377,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12115,6 +14392,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12144,6 +14422,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -12171,7 +14535,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -12185,6 +14550,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12204,6 +14570,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -12261,7 +14661,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -12275,6 +14676,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12297,6 +14699,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -12313,6 +14716,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12352,7 +14756,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12366,6 +14771,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12395,6 +14801,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -12422,7 +14914,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -12436,6 +14929,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12455,6 +14949,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -12530,7 +15058,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12544,6 +15073,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12573,6 +15103,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -12600,7 +15216,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -12614,6 +15231,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12633,6 +15251,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -12696,6 +15348,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -12712,6 +15365,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12751,7 +15405,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12765,6 +15420,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12794,6 +15450,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -12821,7 +15563,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -12835,6 +15578,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12854,6 +15598,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -12911,14 +15689,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12932,6 +15712,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -12983,7 +15764,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -12997,6 +15779,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13026,6 +15809,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -13053,7 +15922,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -13067,6 +15937,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13086,6 +15957,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -13151,7 +16056,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -13159,6 +16065,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -13185,6 +16092,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13231,7 +16139,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -13239,6 +16148,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -13265,6 +16175,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13311,7 +16222,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -13319,6 +16231,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -13335,6 +16248,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13364,6 +16278,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -13379,7 +16294,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -13395,7 +16311,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -13415,6 +16332,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -13433,7 +16351,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -13456,7 +16375,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -13490,6 +16410,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -13508,7 +16429,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -13531,7 +16453,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -13564,7 +16487,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -13587,7 +16511,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -13610,7 +16535,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -13618,6 +16544,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -13653,7 +16580,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -13661,6 +16589,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -13803,6 +16732,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -13821,7 +16751,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -13866,7 +16797,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "views" }, { "method": "attrInt", @@ -13923,7 +16855,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -13936,6 +16869,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -13999,7 +16946,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -14134,6 +17082,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -14150,6 +17099,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14189,7 +17139,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -14203,6 +17154,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14232,6 +17184,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -14259,7 +17297,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -14273,6 +17312,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14292,6 +17332,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -14349,7 +17423,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -14363,6 +17438,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14385,6 +17461,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -14401,6 +17478,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14440,7 +17518,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -14454,6 +17533,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14483,6 +17563,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -14510,7 +17676,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -14524,6 +17691,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14543,6 +17711,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -14618,7 +17820,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -14632,6 +17835,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14661,6 +17865,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -14688,7 +17978,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -14702,6 +17993,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14721,6 +18013,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -14784,6 +18110,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -14800,6 +18127,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14839,7 +18167,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -14853,6 +18182,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14882,6 +18212,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -14909,7 +18325,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -14923,6 +18340,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -14942,6 +18360,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -14999,14 +18451,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -15020,6 +18474,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15071,7 +18526,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -15085,6 +18541,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15114,6 +18571,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -15141,7 +18684,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -15155,6 +18699,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15174,6 +18719,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -15239,7 +18818,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -15247,6 +18827,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -15273,6 +18854,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15319,7 +18901,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -15327,6 +18910,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -15353,6 +18937,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15399,7 +18984,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -15407,6 +18993,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -15423,6 +19010,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15452,6 +19040,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -15467,7 +19056,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -15483,7 +19073,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -15503,6 +19094,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -15521,7 +19113,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -15544,7 +19137,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -15578,6 +19172,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -15596,7 +19191,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -15619,7 +19215,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -15652,7 +19249,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -15675,7 +19273,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -15698,7 +19297,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -15706,6 +19306,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -15741,7 +19342,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -15749,6 +19351,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -15891,6 +19494,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -15909,7 +19513,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -15954,7 +19559,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "views" }, { "method": "attrInt", @@ -16011,7 +19617,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -16047,14 +19654,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -16064,7 +19673,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -16072,6 +19682,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16094,14 +19718,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "features-disabled" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "451" } ], "repeats": false @@ -16111,7 +19737,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -16119,6 +19746,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16141,14 +19782,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -16158,7 +19801,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -16166,6 +19810,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16188,14 +19846,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -16205,7 +19865,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -16213,6 +19874,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16235,14 +19910,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -16252,7 +19929,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -16260,6 +19938,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16280,6 +19972,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -16299,14 +20005,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -16316,7 +20024,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -16437,7 +20146,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -16495,7 +20205,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -16630,6 +20341,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -16646,6 +20358,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16685,7 +20398,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -16699,6 +20413,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16728,6 +20443,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -16755,7 +20556,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -16769,6 +20571,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16788,6 +20591,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -16845,7 +20682,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -16859,6 +20697,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16881,6 +20720,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -16897,6 +20737,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16936,7 +20777,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -16950,6 +20792,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -16979,6 +20822,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -17006,7 +20935,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -17020,6 +20950,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17039,6 +20970,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -17114,7 +21079,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -17128,6 +21094,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17157,6 +21124,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -17184,7 +21237,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -17198,6 +21252,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17217,6 +21272,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -17280,6 +21369,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -17296,6 +21386,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17335,7 +21426,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -17349,6 +21441,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17378,6 +21471,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -17405,7 +21584,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -17419,6 +21599,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17438,6 +21619,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -17495,14 +21710,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -17516,6 +21733,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17567,7 +21785,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -17581,6 +21800,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17610,6 +21830,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -17637,7 +21943,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -17651,6 +21958,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17670,6 +21978,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -17735,7 +22077,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -17743,6 +22086,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -17769,6 +22113,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17815,7 +22160,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -17823,6 +22169,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -17849,6 +22196,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17895,7 +22243,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -17903,6 +22252,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -17919,6 +22269,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17948,6 +22299,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -17963,7 +22315,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -17979,7 +22332,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -17999,6 +22353,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -18017,7 +22372,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -18040,7 +22396,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -18074,6 +22431,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -18092,7 +22450,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -18115,7 +22474,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -18148,7 +22508,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -18171,7 +22532,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -18194,7 +22556,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -18202,6 +22565,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -18237,7 +22601,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -18245,6 +22610,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -18387,6 +22753,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -18405,7 +22772,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -18450,7 +22818,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "views" }, { "method": "attrInt", @@ -18510,6 +22879,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -18529,7 +22905,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -18587,7 +22964,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -18722,6 +23100,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -18738,6 +23117,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -18777,7 +23157,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -18791,6 +23172,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -18820,6 +23202,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -18847,7 +23315,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -18861,6 +23330,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -18880,6 +23350,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -18937,7 +23441,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -18951,6 +23456,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -18973,6 +23479,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -18989,6 +23496,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19028,7 +23536,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -19042,6 +23551,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19071,6 +23581,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -19098,7 +23694,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -19112,6 +23709,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19131,6 +23729,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -19206,7 +23838,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -19220,6 +23853,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19249,6 +23883,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -19276,7 +23996,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -19290,6 +24011,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19309,6 +24031,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -19372,6 +24128,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -19388,6 +24145,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19427,7 +24185,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -19441,6 +24200,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19470,6 +24230,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -19497,7 +24343,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -19511,6 +24358,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19530,6 +24378,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -19587,14 +24469,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -19608,6 +24492,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19659,7 +24544,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -19673,6 +24559,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19702,6 +24589,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -19729,7 +24702,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -19743,6 +24717,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19762,6 +24737,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -19827,7 +24836,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -19835,6 +24845,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -19861,6 +24872,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19907,7 +24919,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -19915,6 +24928,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -19941,6 +24955,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -19987,7 +25002,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -19995,6 +25011,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -20011,6 +25028,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -20040,6 +25058,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -20055,7 +25074,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -20071,7 +25091,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -20091,6 +25112,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -20109,7 +25131,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -20132,7 +25155,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -20166,6 +25190,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -20184,7 +25209,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -20207,7 +25233,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -20240,7 +25267,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -20263,7 +25291,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -20286,7 +25315,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -20294,6 +25324,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -20329,7 +25360,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -20337,6 +25369,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -20479,6 +25512,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -20497,7 +25531,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -20542,7 +25577,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "views" }, { "method": "attrInt", @@ -20625,14 +25661,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -20642,7 +25680,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20650,6 +25689,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20672,14 +25725,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "features-disabled" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "451" } ], "repeats": false @@ -20689,7 +25744,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20697,6 +25753,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20719,14 +25789,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -20736,7 +25808,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20744,6 +25817,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20766,14 +25853,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -20783,7 +25872,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20791,6 +25881,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20813,14 +25917,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -20830,7 +25936,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20838,6 +25945,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20860,14 +25981,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "repeats": false @@ -20877,7 +26000,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -20885,6 +26009,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -20905,6 +26043,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -20924,7 +26069,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "child", @@ -20938,14 +26084,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -21005,7 +26153,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -21052,6 +26201,7 @@ "wireName": "is_sender", "type": "string", "required": false, + "literalValue": "true", "sourcePath": [ "message" ] @@ -21086,7 +26236,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "attrString", @@ -21094,6 +26245,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "response", "sourcePath": [ "meta" ] @@ -21180,6 +26332,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -21199,7 +26358,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -21246,6 +26406,7 @@ "wireName": "is_sender", "type": "string", "required": false, + "literalValue": "true", "sourcePath": [ "message" ] @@ -21280,7 +26441,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "attrString", @@ -21288,6 +26450,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "response", "sourcePath": [ "meta" ] @@ -21397,14 +26560,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -21414,7 +26579,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21422,6 +26588,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21444,14 +26624,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "repeats": false @@ -21461,7 +26643,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21469,6 +26652,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21491,14 +26688,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -21508,7 +26707,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21516,6 +26716,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21538,14 +26752,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -21555,7 +26771,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21563,6 +26780,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21585,14 +26816,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -21602,7 +26835,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21610,6 +26844,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21632,14 +26880,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "repeats": false @@ -21649,7 +26899,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -21657,6 +26908,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -21677,6 +26942,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -21696,7 +26968,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "child", @@ -21710,14 +26983,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -21821,7 +27096,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -21843,14 +27119,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -21896,7 +27174,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -21920,6 +27199,28 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AUDIO_GIF_IMAGE_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "audio", + "value": "audio" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -21947,7 +27248,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -21980,7 +27282,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -22003,14 +27306,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -22117,6 +27422,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -22136,6 +27442,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question_reshare", "sourcePath": [ "meta" ] @@ -22213,6 +27520,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "views", "sourcePath": [ "views_count" ] @@ -22263,7 +27571,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -22276,6 +27585,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -22339,7 +27662,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -22361,14 +27685,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -22414,7 +27740,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -22438,6 +27765,28 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AUDIO_GIF_IMAGE_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "audio", + "value": "audio" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -22465,7 +27814,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -22498,7 +27848,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -22521,14 +27872,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -22635,6 +27988,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -22654,6 +28008,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question_reshare", "sourcePath": [ "meta" ] @@ -22731,6 +28086,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "views", "sourcePath": [ "views_count" ] @@ -22781,7 +28137,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -22817,14 +28174,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -22834,7 +28193,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -22842,6 +28202,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -22864,14 +28238,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "features-disabled" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "451" } ], "repeats": false @@ -22881,7 +28257,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -22889,6 +28266,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -22911,14 +28302,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -22928,7 +28321,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -22936,6 +28330,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -22958,14 +28366,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -22975,7 +28385,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -22983,6 +28394,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -23005,14 +28430,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -23022,7 +28449,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -23030,6 +28458,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -23050,6 +28492,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -23069,14 +28525,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -23086,7 +28544,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -23207,7 +28666,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -23265,7 +28725,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -23287,14 +28748,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -23340,7 +28803,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -23364,6 +28828,28 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AUDIO_GIF_IMAGE_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "audio", + "value": "audio" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -23391,7 +28877,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -23424,7 +28911,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -23447,14 +28935,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -23561,6 +29051,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -23580,6 +29071,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question_reshare", "sourcePath": [ "meta" ] @@ -23657,6 +29149,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "views", "sourcePath": [ "views_count" ] @@ -23710,6 +29203,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -23729,7 +29229,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -23787,7 +29288,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -23809,14 +29311,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -23862,7 +29366,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -23886,6 +29391,28 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AUDIO_GIF_IMAGE_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "audio", + "value": "audio" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -23913,7 +29440,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -23946,7 +29474,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -23969,14 +29498,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -24083,6 +29614,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -24102,6 +29634,7 @@ "wireName": "interaction_type", "type": "string", "required": true, + "literalValue": "question_reshare", "sourcePath": [ "meta" ] @@ -24179,6 +29712,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "views", "sourcePath": [ "views_count" ] @@ -24255,14 +29789,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -24272,7 +29808,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24280,6 +29817,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24302,14 +29853,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "features-disabled" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "451" } ], "repeats": false @@ -24319,7 +29872,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24327,6 +29881,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24349,14 +29917,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -24366,7 +29936,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24374,6 +29945,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24396,14 +29981,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -24413,7 +30000,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24421,6 +30009,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24443,14 +30045,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -24460,7 +30064,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24468,6 +30073,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24490,14 +30109,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "repeats": false @@ -24507,7 +30128,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24515,6 +30137,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24535,6 +30171,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -24554,7 +30197,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "child", @@ -24568,14 +30212,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -24628,7 +30274,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -24748,6 +30395,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24760,7 +30421,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -24903,14 +30565,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -24920,7 +30584,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24928,6 +30593,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24950,14 +30629,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "repeats": false @@ -24967,7 +30648,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -24975,6 +30657,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -24997,14 +30693,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -25014,7 +30712,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25022,6 +30721,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25044,14 +30757,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -25061,7 +30776,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25069,6 +30785,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25089,6 +30819,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25108,14 +30852,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -25125,7 +30871,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -25182,6 +30929,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "status", "sourcePath": [ "my_addons" ] @@ -25191,7 +30939,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -25272,6 +31021,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25285,6 +31048,7 @@ "wireName": "type", "type": "string", "required": true, + "literalValue": "status", "sourcePath": [ "my_addons" ] @@ -25294,7 +31058,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -25398,14 +31163,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -25415,7 +31182,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25423,6 +31191,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25445,14 +31227,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "repeats": false @@ -25462,7 +31246,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25470,6 +31255,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25492,14 +31291,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -25509,7 +31310,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25517,6 +31319,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25539,14 +31355,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -25556,7 +31374,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25564,6 +31383,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25584,6 +31417,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25603,14 +31450,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -25620,7 +31469,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -25671,7 +31521,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -25684,6 +31535,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25708,7 +31573,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -25744,14 +31610,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -25761,7 +31629,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25769,6 +31638,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25791,14 +31674,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "features-disabled" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "451" } ], "repeats": false @@ -25808,7 +31693,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25816,6 +31702,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25838,14 +31738,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "repeats": false @@ -25855,7 +31757,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25863,6 +31766,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25885,14 +31802,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "repeats": false @@ -25902,7 +31821,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25910,6 +31830,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25932,14 +31866,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "repeats": false @@ -25949,7 +31885,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ], "assertions": [ @@ -25957,6 +31894,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25977,6 +31928,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -25996,14 +31961,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -26013,7 +31980,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -26059,7 +32027,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -26155,10 +32124,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -26167,7 +32150,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -26263,10 +32247,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -26275,7 +32273,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -26290,11 +32289,34 @@ "tag": "GetOptOutListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInvalidRequest", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 429 + ], + "errorTexts": [ + "bad-request", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -26307,7 +32329,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -26323,14 +32346,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -26342,6 +32367,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -26353,14 +32383,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -26372,6 +32404,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -26386,11 +32423,34 @@ "tag": "GetOptOutListResponseInternalServerError", "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInternalServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 501 + ], + "errorTexts": [ + "feature-not-implemented", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -26403,7 +32463,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -26419,14 +32480,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -26438,6 +32501,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -26449,14 +32517,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -26468,6 +32538,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -26554,7 +32629,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -26562,6 +32638,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -26660,10 +32737,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -26672,7 +32763,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -26680,6 +32772,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "list" ] @@ -26778,10 +32871,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -26790,7 +32897,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -26798,6 +32906,7 @@ "wireName": "matched", "type": "string", "required": true, + "literalValue": "false", "sourcePath": [ "list" ] @@ -26898,11 +33007,38 @@ "tag": "UpdateOptOutListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseInvalidRequest", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 406, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "not-acceptable", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -26915,7 +33051,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -26931,14 +33068,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -26974,6 +33113,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -26985,14 +33129,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -27004,6 +33150,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -27015,14 +33166,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -27034,6 +33187,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -27045,14 +33203,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -27064,6 +33224,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -27078,11 +33243,34 @@ "tag": "UpdateOptOutListResponseServerError", "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 501 + ], + "errorTexts": [ + "feature-not-implemented", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -27095,7 +33283,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -27111,14 +33300,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -27130,6 +33321,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -27141,14 +33337,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -27160,6 +33358,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -27205,7 +33408,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrJidWithType", @@ -27225,6 +33429,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -27237,7 +33448,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrJidWithType", @@ -27283,7 +33495,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrJidWithType", @@ -27303,6 +33516,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -27315,7 +33535,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrJidWithType", @@ -27362,6 +33583,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "privacy" ] @@ -27378,7 +33600,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -27464,6 +33687,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -27477,6 +33707,7 @@ "wireName": "addressing_mode", "type": "string", "required": true, + "literalValue": "lid", "sourcePath": [ "privacy" ] @@ -27493,7 +33724,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -27579,6 +33811,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -27592,6 +33831,7 @@ "wireName": "addressing_mode", "type": "string", "required": false, + "literalValue": "pn", "sourcePath": [ "privacy" ] @@ -27608,7 +33848,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -27660,11 +33901,32 @@ "tag": "GetContactBlacklistResponseError", "moduleName": "WASmaxInPrivacyGetContactBlacklistResponseError", "kind": "error", + "errorCodes": [ + 400, + 429, + 500, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "internal-server-error", + "rate-overlimit", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -27684,7 +33946,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -27700,14 +33963,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -27719,6 +33984,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -27730,14 +34000,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -27749,6 +34021,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -27760,14 +34037,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -27779,6 +34058,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -27790,14 +34074,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -27809,6 +34095,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -27820,14 +34111,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -27839,6 +34132,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -28536,7 +34834,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -28615,10 +34914,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] } ], "fields": [ @@ -28627,7 +34940,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -28701,11 +35015,35 @@ "tag": "SignCredentialResponseErrorNoRetry", "moduleName": "WASmaxInPrivatestatsSignCredentialResponseErrorNoRetry", "kind": "error", + "errorCodes": [ + 400, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -28718,7 +35056,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -28734,14 +35073,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -28753,6 +35094,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -28764,14 +35110,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -28783,6 +35131,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -28794,14 +35147,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -28813,6 +35168,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -28832,6 +35192,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -28851,14 +35225,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -28868,7 +35244,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -29046,7 +35423,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29078,6 +35456,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29097,7 +35482,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29124,11 +35510,33 @@ "tag": "GroupReportResponseError", "moduleName": "WASmaxInSpamGroupReportResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 500, + 501, + 548 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29148,7 +35556,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -29164,14 +35573,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -29183,6 +35594,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -29194,14 +35610,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -29213,6 +35631,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -29224,14 +35647,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -29243,6 +35668,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -29254,14 +35684,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -29273,6 +35705,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -29291,13 +35728,19 @@ "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "548" } ], "assertions": [ { "kind": "tag", "name": "error" + }, + { + "kind": "attr", + "name": "code", + "value": "548" } ] }, @@ -29309,14 +35752,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -29328,6 +35773,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -29419,7 +35869,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29451,6 +35902,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29470,7 +35928,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29497,11 +35956,33 @@ "tag": "IndividualReportResponseError", "moduleName": "WASmaxInSpamIndividualReportResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 500, + 501, + 548 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29521,7 +36002,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -29537,14 +36019,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -29556,6 +36040,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -29567,14 +36056,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -29586,6 +36077,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -29597,14 +36093,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -29616,6 +36114,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -29627,14 +36130,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -29646,6 +36151,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -29664,13 +36174,19 @@ "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "548" } ], "assertions": [ { "kind": "tag", "name": "error" + }, + { + "kind": "attr", + "name": "code", + "value": "548" } ] }, @@ -29682,14 +36198,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -29701,6 +36219,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -29783,7 +36306,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29815,6 +36339,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29834,7 +36365,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -29861,11 +36393,33 @@ "tag": "NewsletterReportResponseError", "moduleName": "WASmaxInSpamNewsletterReportResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 500, + 501, + 548 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -29885,7 +36439,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -29901,14 +36456,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -29920,6 +36477,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -29931,14 +36493,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -29950,6 +36514,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -29961,14 +36530,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -29980,6 +36551,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -29991,14 +36567,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -30010,6 +36588,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -30028,13 +36611,19 @@ "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "548" } ], "assertions": [ { "kind": "tag", "name": "error" + }, + { + "kind": "attr", + "name": "code", + "value": "548" } ] }, @@ -30046,14 +36635,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -30065,6 +36656,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -30141,7 +36737,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -30173,6 +36770,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -30192,7 +36796,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -30219,11 +36824,33 @@ "tag": "StatusReportResponseError", "moduleName": "WASmaxInSpamStatusReportResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 500, + 501, + 548 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -30243,7 +36870,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -30259,14 +36887,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -30278,6 +36908,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -30289,14 +36924,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -30308,6 +36945,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -30319,14 +36961,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -30338,6 +36982,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -30349,14 +36998,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -30368,6 +37019,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -30386,13 +37042,19 @@ "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "548" } ], "assertions": [ { "kind": "tag", "name": "error" + }, + { + "kind": "attr", + "name": "code", + "value": "548" } ] }, @@ -30404,14 +37066,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -30423,6 +37087,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -30493,7 +37162,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -30525,6 +37195,13 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -30544,7 +37221,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -30571,11 +37249,30 @@ "tag": "StatusReportV2ResponseError", "moduleName": "WASmaxInSpamStatusReportV2ResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 429, + 500 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, { "kind": "attr", "name": "type", @@ -30595,7 +37292,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -30611,14 +37309,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -30630,6 +37330,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -30641,14 +37346,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -30660,6 +37367,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -30671,14 +37383,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -30690,6 +37404,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -30701,14 +37420,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -30720,6 +37441,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -30819,7 +37545,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -30879,6 +37606,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -30891,7 +37632,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -30951,6 +37693,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -30970,14 +37726,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -30987,7 +37745,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -30995,11 +37754,33 @@ "tag": "GetDisclosureStageByIdsResponseServerError", "moduleName": "WASmaxInUserNoticeGetDisclosureStageByIdsResponseServerError", "kind": "error", + "errorCodes": [ + 429, + 500 + ], + "errorTexts": [ + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31012,7 +37793,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -31028,14 +37810,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -31047,6 +37831,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -31058,14 +37847,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -31077,6 +37868,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -31128,7 +37924,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -31188,6 +37985,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31200,7 +38011,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -31260,6 +38072,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31279,14 +38105,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "repeats": false @@ -31296,7 +38124,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -31304,11 +38133,33 @@ "tag": "GetDisclosuresResponseServerError", "moduleName": "WASmaxInUserNoticeGetDisclosuresResponseServerError", "kind": "error", + "errorCodes": [ + 429, + 500 + ], + "errorTexts": [ + "internal-server-error", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31321,7 +38172,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -31337,14 +38189,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -31356,6 +38210,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -31367,14 +38226,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -31386,6 +38247,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -31525,6 +38391,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31537,7 +38417,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -31620,6 +38501,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -31632,7 +38527,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -31944,6 +38840,7 @@ "wireName": "outage", "type": "string", "required": false, + "literalValue": "1", "sourcePath": [ "accept_pay" ] @@ -31954,6 +38851,7 @@ "wireName": "sandbox", "type": "string", "required": false, + "literalValue": "1", "sourcePath": [ "accept_pay" ] @@ -31963,7 +38861,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -31979,7 +38878,8 @@ "name": "service", "wireName": "service", "type": "string", - "required": true + "required": true, + "literalValue": "FBPAY" }, { "method": "child", @@ -31993,7 +38893,29 @@ "name": "notice", "wireName": "notice", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_BRP2PCONSENT_BRPAYPRIVACYPOLICY_BRPAYTOS_BRPAYWATOS", + "module": "WASmaxInAccountEnums", + "variants": [ + { + "name": "br_p2p_consent", + "value": "br_p2p_consent" + }, + { + "name": "br_pay_privacy_policy", + "value": "br_pay_privacy_policy" + }, + { + "name": "br_pay_tos", + "value": "br_pay_tos" + }, + { + "name": "br_pay_wa_tos", + "value": "br_pay_wa_tos" + } + ] + } } ], "repeats": true @@ -32019,7 +38941,8 @@ "name": "service", "wireName": "service", "type": "string", - "required": true + "required": true, + "literalValue": "UPI" }, { "method": "child", @@ -32033,7 +38956,21 @@ "name": "notice", "wireName": "notice", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYTOSV3_UPIPAYPRIVACYPOLICY", + "module": "WASmaxInAccountEnums", + "variants": [ + { + "name": "pay_tos_v3", + "value": "pay_tos_v3" + }, + { + "name": "upi_pay_privacy_policy", + "value": "upi_pay_privacy_policy" + } + ] + } } ], "repeats": true @@ -32067,6 +39004,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -32080,6 +39031,7 @@ "wireName": "outage", "type": "string", "required": false, + "literalValue": "1", "sourcePath": [ "accept_pay" ] @@ -32090,6 +39042,7 @@ "wireName": "sandbox", "type": "string", "required": false, + "literalValue": "1", "sourcePath": [ "accept_pay" ] @@ -32099,7 +39052,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -32115,7 +39069,8 @@ "name": "service", "wireName": "service", "type": "string", - "required": true + "required": true, + "literalValue": "FBPAY" }, { "method": "child", @@ -32129,7 +39084,29 @@ "name": "notice", "wireName": "notice", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_BRP2PCONSENT_BRPAYPRIVACYPOLICY_BRPAYTOS_BRPAYWATOS", + "module": "WASmaxInAccountEnums", + "variants": [ + { + "name": "br_p2p_consent", + "value": "br_p2p_consent" + }, + { + "name": "br_pay_privacy_policy", + "value": "br_pay_privacy_policy" + }, + { + "name": "br_pay_tos", + "value": "br_pay_tos" + }, + { + "name": "br_pay_wa_tos", + "value": "br_pay_wa_tos" + } + ] + } } ], "repeats": true @@ -32155,7 +39132,8 @@ "name": "service", "wireName": "service", "type": "string", - "required": true + "required": true, + "literalValue": "UPI" }, { "method": "child", @@ -32169,7 +39147,21 @@ "name": "notice", "wireName": "notice", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYTOSV3_UPIPAYPRIVACYPOLICY", + "module": "WASmaxInAccountEnums", + "variants": [ + { + "name": "pay_tos_v3", + "value": "pay_tos_v3" + }, + { + "name": "upi_pay_privacy_policy", + "value": "upi_pay_privacy_policy" + } + ] + } } ], "repeats": true @@ -32198,11 +39190,41 @@ "tag": "SetPaymentsTOSv3ResponseError", "moduleName": "WASmaxInAccountSetPaymentsTOSv3ResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 443, + 453, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "config-mismatch", + "forbidden", + "internal-server-error", + "service-unavailable", + "upgrade-required" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -32215,7 +39237,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -32231,14 +39254,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -32250,6 +39275,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -32261,14 +39291,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -32280,6 +39312,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -32291,14 +39328,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "upgrade-required" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "443" } ], "assertions": [ @@ -32310,6 +39349,11 @@ "kind": "attr", "name": "text", "value": "upgrade-required" + }, + { + "kind": "attr", + "name": "code", + "value": "443" } ] }, @@ -32321,14 +39365,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "config-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "453" } ], "assertions": [ @@ -32340,6 +39386,11 @@ "kind": "attr", "name": "text", "value": "config-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "453" } ] }, @@ -32351,14 +39402,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -32370,6 +39423,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -32381,14 +39439,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -32400,6 +39460,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] } @@ -32876,7 +39941,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -32889,6 +39955,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -32901,7 +39981,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -32914,6 +39995,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -32933,14 +40028,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "repeats": false @@ -32950,7 +40047,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] }, @@ -32963,6 +40061,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -32982,14 +40094,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "repeats": false @@ -32999,7 +40113,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -33461,7 +40576,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -33475,14 +40591,42 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } }, { "method": "maybeAttrEnum", "name": "shouldShowPrivacyInterstitialToNewUsers", "wireName": "should_show_privacy_interstitial_to_new_users", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "maybeAttrString", @@ -33506,7 +40650,29 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_PAUSED_SUCCESS_WARNING", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "PAUSED", + "value": "PAUSED" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + }, + { + "name": "WARNING", + "value": "WARNING" + } + ] + } }, { "method": "maybeAttrInt", @@ -33530,14 +40696,42 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } }, { "method": "maybeAttrEnum", "name": "v1Enabled", "wireName": "v1_enabled", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -33554,7 +40748,21 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } } ], "repeats": false @@ -33571,7 +40779,21 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } } ], "repeats": false @@ -33588,7 +40810,25 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ELIGIBLETOONBOARD_NOTELIGIBLE_ONBOARDED", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "ELIGIBLE_TO_ONBOARD", + "value": "ELIGIBLE_TO_ONBOARD" + }, + { + "name": "NOT_ELIGIBLE", + "value": "NOT_ELIGIBLE" + }, + { + "name": "ONBOARDED", + "value": "ONBOARDED" + } + ] + } } ], "repeats": false @@ -33604,6 +40844,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -33623,7 +40877,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -33637,14 +40892,42 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } }, { "method": "maybeAttrEnum", "name": "shouldShowPrivacyInterstitialToNewUsers", "wireName": "should_show_privacy_interstitial_to_new_users", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "maybeAttrString", @@ -33668,7 +40951,29 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_PAUSED_SUCCESS_WARNING", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "PAUSED", + "value": "PAUSED" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + }, + { + "name": "WARNING", + "value": "WARNING" + } + ] + } }, { "method": "maybeAttrInt", @@ -33692,14 +40997,42 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } }, { "method": "maybeAttrEnum", "name": "v1Enabled", "wireName": "v1_enabled", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -33716,7 +41049,21 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } } ], "repeats": false @@ -33733,7 +41080,21 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAIL_SUCCESS", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "FAIL", + "value": "FAIL" + }, + { + "name": "SUCCESS", + "value": "SUCCESS" + } + ] + } } ], "repeats": false @@ -33750,7 +41111,25 @@ "name": "status", "wireName": "status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ELIGIBLETOONBOARD_NOTELIGIBLE_ONBOARDED", + "module": "WASmaxInBizMarketingMessageEnums", + "variants": [ + { + "name": "ELIGIBLE_TO_ONBOARD", + "value": "ELIGIBLE_TO_ONBOARD" + }, + { + "name": "NOT_ELIGIBLE", + "value": "NOT_ELIGIBLE" + }, + { + "name": "ONBOARDED", + "value": "ONBOARDED" + } + ] + } } ], "repeats": false @@ -33761,11 +41140,37 @@ "tag": "GetBusinessEligibilityResponseError", "moduleName": "WASmaxInBizMarketingMessageGetBusinessEligibilityResponseError", "kind": "error", + "errorCodes": [ + 400, + 403, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -33785,7 +41190,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -33801,14 +41207,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -33820,6 +41228,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -33831,14 +41244,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -33850,6 +41265,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -33861,14 +41281,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -33880,6 +41302,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -33891,14 +41318,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -33910,6 +41339,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -33963,6 +41397,24 @@ "wireName": "value", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_NOTSET_TRUE", + "module": "WASmaxInBizSettingsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "notset", + "value": "notset" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "smb_data_sharing_with_meta_consent" ] @@ -33985,7 +41437,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -33998,6 +41451,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -34018,6 +41485,24 @@ "wireName": "value", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_NOTSET_TRUE", + "module": "WASmaxInBizSettingsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "notset", + "value": "notset" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "smb_data_sharing_with_meta_consent" ] @@ -34040,7 +41525,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -34048,11 +41534,37 @@ "tag": "GetPrivacySettingResponseError", "moduleName": "WASmaxInBizSettingsGetPrivacySettingResponseError", "kind": "error", + "errorCodes": [ + 400, + 500, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -34065,7 +41577,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -34081,14 +41594,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -34100,6 +41615,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -34111,14 +41631,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -34130,6 +41652,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -34141,14 +41668,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -34160,6 +41689,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -34171,14 +41705,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -34190,6 +41726,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -34393,6 +41934,20 @@ "wireName": "is_eligible", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "integrity" ] @@ -34439,7 +41994,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -34460,7 +42016,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FREEMSG_PERCENTAGE", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "free_msg", + "value": "free_msg" + }, + { + "name": "percentage", + "value": "percentage" + } + ] + } }, { "method": "maybeAttrInt", @@ -34542,7 +42112,37 @@ "name": "value", "wireName": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ALREADYCLAIMED_EXPIRED_INVALID_NOTFOUND_NOTOWNED_VALID", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "already_claimed", + "value": "already_claimed" + }, + { + "name": "expired", + "value": "expired" + }, + { + "name": "invalid", + "value": "invalid" + }, + { + "name": "not_found", + "value": "not_found" + }, + { + "name": "not_owned", + "value": "not_owned" + }, + { + "name": "valid", + "value": "valid" + } + ] + } } ], "repeats": false @@ -34558,6 +42158,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -34661,6 +42275,20 @@ "wireName": "is_eligible", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "integrity" ] @@ -34707,7 +42335,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -34728,7 +42357,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FREEMSG_PERCENTAGE", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "free_msg", + "value": "free_msg" + }, + { + "name": "percentage", + "value": "percentage" + } + ] + } }, { "method": "maybeAttrInt", @@ -34810,7 +42453,37 @@ "name": "value", "wireName": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ALREADYCLAIMED_EXPIRED_INVALID_NOTFOUND_NOTOWNED_VALID", + "module": "WASmaxInSmbMeteredMessagingAccountEnums", + "variants": [ + { + "name": "already_claimed", + "value": "already_claimed" + }, + { + "name": "expired", + "value": "expired" + }, + { + "name": "invalid", + "value": "invalid" + }, + { + "name": "not_found", + "value": "not_found" + }, + { + "name": "not_owned", + "value": "not_owned" + }, + { + "name": "valid", + "value": "valid" + } + ] + } } ], "repeats": false @@ -34821,11 +42494,43 @@ "tag": "GetSMBMeteredMessagingCheckoutResponseError", "moduleName": "WASmaxInSmbMeteredMessagingAccountGetSMBMeteredMessagingCheckoutResponseError", "kind": "error", + "errorCodes": [ + 400, + 401, + 405, + 429, + 482, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-limit", + "internal-server-error", + "not-allowed", + "not-authorized", + "rate-overlimit", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -34845,7 +42550,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -34861,14 +42567,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -34880,6 +42588,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -34891,14 +42604,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -34910,6 +42625,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -34921,14 +42641,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -34940,6 +42662,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -34951,14 +42678,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -34970,6 +42699,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -34981,14 +42715,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -35000,6 +42736,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -35011,14 +42752,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "482" } ], "assertions": [ @@ -35030,6 +42773,11 @@ "kind": "attr", "name": "text", "value": "feature-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "482" } ] }, @@ -35041,14 +42789,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -35060,6 +42810,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -35113,6 +42868,24 @@ "wireName": "value", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_NOTSET_TRUE", + "module": "WASmaxInBizSettingsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "notset", + "value": "notset" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "smb_data_sharing_with_meta_consent" ] @@ -35135,7 +42908,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -35148,6 +42922,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -35168,6 +42956,24 @@ "wireName": "value", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_NOTSET_TRUE", + "module": "WASmaxInBizSettingsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "notset", + "value": "notset" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "smb_data_sharing_with_meta_consent" ] @@ -35190,7 +42996,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -35198,11 +43005,37 @@ "tag": "SetPrivacySettingResponseError", "moduleName": "WASmaxInBizSettingsSetPrivacySettingResponseError", "kind": "error", + "errorCodes": [ + 400, + 500, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "internal-server-error", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -35215,7 +43048,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -35231,14 +43065,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -35250,6 +43086,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -35261,14 +43102,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -35280,6 +43123,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -35291,14 +43139,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -35310,6 +43160,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -35321,14 +43176,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -35340,6 +43197,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -36034,7 +43896,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -36047,6 +43910,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36059,7 +43936,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -36067,11 +43945,38 @@ "tag": "UpdatePreferenceResponseInvalidRequest", "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseInvalidRequest", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 406, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "not-acceptable", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36084,7 +43989,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -36100,14 +44006,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -36143,6 +44051,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -36154,14 +44067,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -36173,6 +44088,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -36184,14 +44104,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -36203,6 +44125,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -36214,14 +44141,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -36233,6 +44162,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -36247,11 +44181,34 @@ "tag": "UpdatePreferenceResponseServerError", "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 501 + ], + "errorTexts": [ + "feature-not-implemented", + "internal-server-error" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36264,7 +44221,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -36280,14 +44238,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -36299,6 +44259,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -36310,14 +44275,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -36329,6 +44296,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] } @@ -36395,7 +44367,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -36408,6 +44381,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36420,7 +44407,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -36428,11 +44416,35 @@ "tag": "EventResponseError", "moduleName": "WASmaxInInAppCommsEventResponseError", "kind": "error", + "errorCodes": [ + 408, + 500, + 503 + ], + "errorTexts": [ + "internal-server-error", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36445,7 +44457,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -36461,14 +44474,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -36480,6 +44495,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -36491,14 +44511,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -36510,6 +44532,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -36521,14 +44548,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -36540,6 +44569,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -36593,6 +44627,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_BLOCKED_UNBLOCKED", + "module": "WASmaxInPsaEnums", + "variants": [ + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "unblocked", + "value": "unblocked" + } + ] + }, "sourcePath": [ "blocking" ] @@ -36602,7 +44650,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -36615,6 +44664,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36628,6 +44691,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_BLOCKED_UNBLOCKED", + "module": "WASmaxInPsaEnums", + "variants": [ + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "unblocked", + "value": "unblocked" + } + ] + }, "sourcePath": [ "blocking" ] @@ -36637,7 +44714,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -36645,11 +44723,37 @@ "tag": "ChatBlockGetResponseServerError", "moduleName": "WASmaxInPsaChatBlockGetResponseServerError", "kind": "error", + "errorCodes": [ + 408, + 429, + 500, + 503 + ], + "errorTexts": [ + "internal-server-error", + "rate-overlimit", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36662,7 +44766,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -36678,14 +44783,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -36697,6 +44804,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -36708,14 +44820,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -36727,6 +44841,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -36738,14 +44857,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -36757,6 +44878,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -36768,14 +44894,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -36787,6 +44915,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -36839,6 +44972,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_BLOCKED_UNBLOCKED", + "module": "WASmaxInPsaEnums", + "variants": [ + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "unblocked", + "value": "unblocked" + } + ] + }, "sourcePath": [ "blocking" ] @@ -36848,7 +44995,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -36861,6 +45009,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36874,6 +45036,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_BLOCKED_UNBLOCKED", + "module": "WASmaxInPsaEnums", + "variants": [ + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "unblocked", + "value": "unblocked" + } + ] + }, "sourcePath": [ "blocking" ] @@ -36883,7 +45059,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -36891,11 +45068,37 @@ "tag": "ChatBlockSetResponseServerError", "moduleName": "WASmaxInPsaChatBlockSetResponseServerError", "kind": "error", + "errorCodes": [ + 408, + 429, + 500, + 503 + ], + "errorTexts": [ + "internal-server-error", + "rate-overlimit", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -36908,7 +45111,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -36924,14 +45128,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -36943,6 +45149,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -36954,14 +45165,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -36973,6 +45186,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -36984,14 +45202,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -37003,6 +45223,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -37014,14 +45239,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -37033,6 +45260,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -37098,7 +45330,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -37125,7 +45358,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -37144,7 +45395,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -37222,7 +45474,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -37678,7 +45944,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -37738,7 +46005,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -37755,7 +46023,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -37789,7 +46079,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -37826,6 +46117,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -37972,7 +46264,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -38000,7 +46306,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -38292,7 +46612,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -38568,7 +46902,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -38599,7 +46934,8 @@ "name": "truncated", "wireName": "truncated", "type": "string", - "required": true + "required": true, + "literalValue": "true" }, { "method": "attrInt", @@ -38638,7 +46974,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -38668,7 +47005,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -38702,10 +47040,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -38714,7 +47066,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -38741,7 +47094,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -38760,7 +47131,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -38838,7 +47210,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -39294,7 +47680,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -39354,7 +47741,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -39371,7 +47759,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -39405,7 +47815,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -39442,6 +47853,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -39588,7 +48000,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -39616,7 +48042,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -39908,7 +48348,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -40184,7 +48638,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -40215,7 +48670,8 @@ "name": "truncated", "wireName": "truncated", "type": "string", - "required": true + "required": true, + "literalValue": "true" }, { "method": "attrInt", @@ -40254,7 +48710,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -40284,7 +48741,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -40313,11 +48771,34 @@ "tag": "BatchGetGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 429 + ], + "errorTexts": [ + "bad-request", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -40330,7 +48811,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -40346,14 +48828,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -40365,6 +48849,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -40376,14 +48865,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -40395,6 +48886,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -40409,11 +48905,38 @@ "tag": "BatchGetGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -40426,7 +48949,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -40442,14 +48966,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -40461,6 +48987,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -40472,14 +49003,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -40491,6 +49024,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -40502,14 +49040,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -40521,6 +49061,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -40613,7 +49158,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -40636,7 +49182,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -40705,7 +49252,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -41161,7 +49722,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -41221,7 +49783,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -41238,7 +49801,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -41272,7 +49857,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -41309,6 +49895,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -41455,7 +50042,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -41483,7 +50084,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -41775,7 +50390,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -42051,7 +50680,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -42077,7 +50707,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -42096,10 +50744,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -42108,7 +50770,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -42131,7 +50794,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -42200,7 +50864,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -42656,7 +51334,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -42716,7 +51395,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -42733,7 +51413,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -42767,7 +51469,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -42804,6 +51507,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -42950,7 +51654,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -42978,7 +51696,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -43270,7 +52002,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -43546,7 +52292,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -43572,7 +52319,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -43586,11 +52351,42 @@ "tag": "GetGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsGetGroupInfoResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 404, + 410, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "gone", + "item-not-found", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -43603,7 +52399,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -43619,14 +52416,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "gone" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "410" } ], "assertions": [ @@ -43638,6 +52437,11 @@ "kind": "attr", "name": "text", "value": "gone" + }, + { + "kind": "attr", + "name": "code", + "value": "410" } ] }, @@ -43649,14 +52453,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -43668,6 +52474,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -43679,14 +52490,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -43698,6 +52511,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -43709,14 +52527,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -43728,6 +52548,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -43739,14 +52564,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -43758,6 +52585,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -43799,11 +52631,38 @@ "tag": "GetGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsGetGroupInfoResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -43816,7 +52675,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -43832,14 +52692,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -43851,6 +52713,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -43862,14 +52729,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -43881,6 +52750,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -43892,14 +52766,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -43911,6 +52787,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -43976,7 +52857,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -44038,7 +52920,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_PREVIEW", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "preview", + "value": "preview" + } + ] + } }, { "method": "", @@ -44115,7 +53011,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -44138,7 +53035,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "204" } ], "assertions": [ @@ -44161,7 +53059,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -44184,7 +53083,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -44228,6 +53128,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -44240,7 +53154,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -44302,7 +53217,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_IMAGE_PREVIEW", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "preview", + "value": "preview" + } + ] + } }, { "method": "", @@ -44379,7 +53308,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -44402,7 +53332,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "204" } ], "assertions": [ @@ -44425,7 +53356,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -44448,7 +53380,8 @@ "name": "status", "wireName": "status", "type": "string", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -44487,11 +53420,42 @@ "tag": "GetGroupProfilePicturesResponseClientError", "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 404, + 405, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "not-allowed", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -44504,7 +53468,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -44520,14 +53485,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -44539,6 +53506,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -44550,14 +53522,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -44569,6 +53543,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -44580,14 +53559,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -44599,6 +53580,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -44610,14 +53596,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -44629,6 +53617,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -44640,14 +53633,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -44659,6 +53654,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -44700,11 +53700,38 @@ "tag": "GetGroupProfilePicturesResponseServerError", "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -44717,7 +53744,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -44733,14 +53761,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -44752,6 +53782,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -44763,14 +53798,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -44782,6 +53819,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -44793,14 +53835,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -44812,6 +53856,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -44890,7 +53939,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -45068,7 +54118,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -45283,6 +54347,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -45302,7 +54367,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "304" } ], "repeats": false @@ -45368,7 +54434,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -45396,7 +54476,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -45688,7 +54782,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -45970,10 +55078,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -45982,7 +55104,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -46160,7 +55283,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -46375,6 +55512,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -46394,7 +55532,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "304" } ], "repeats": false @@ -46460,7 +55599,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -46488,7 +55641,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -46780,7 +55947,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -47057,11 +56238,48 @@ "tag": "GetInviteGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 406, + 410, + 416, + 423, + 436 + ], + "errorTexts": [ + "bad-request", + "gone", + "growth-locked", + "item-not-found", + "locked", + "not-acceptable", + "not-authorized", + "parent-group-suspended" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -47074,7 +56292,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -47090,14 +56309,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -47109,6 +56330,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -47120,14 +56346,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -47139,6 +56367,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -47150,14 +56383,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "gone" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "410" } ], "assertions": [ @@ -47169,6 +56404,11 @@ "kind": "attr", "name": "text", "value": "gone" + }, + { + "kind": "attr", + "name": "code", + "value": "410" } ] }, @@ -47180,14 +56420,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -47199,6 +56441,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -47210,14 +56457,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -47253,6 +56502,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -47264,14 +56518,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -47283,6 +56539,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -47294,14 +56555,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "growth-locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "436" }, { "method": "child", @@ -47315,7 +56578,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -47337,6 +56601,11 @@ "kind": "attr", "name": "text", "value": "growth-locked" + }, + { + "kind": "attr", + "name": "code", + "value": "436" } ] }, @@ -47348,14 +56617,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "parent-group-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -47367,6 +56638,11 @@ "kind": "attr", "name": "text", "value": "parent-group-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] }, @@ -47408,11 +56684,38 @@ "tag": "GetInviteGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -47425,7 +56728,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -47441,14 +56745,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -47460,6 +56766,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -47471,14 +56782,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -47490,6 +56803,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -47501,14 +56819,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -47520,6 +56840,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -47773,7 +57098,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -47929,7 +57268,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "repeats": false @@ -47947,6 +57300,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -47969,7 +57323,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "304" } ], "repeats": false, @@ -48001,7 +57356,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false, @@ -48021,7 +57377,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false, @@ -48104,7 +57482,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -48132,7 +57524,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -48424,7 +57830,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -48703,7 +58123,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -48716,7 +58137,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -48732,6 +58167,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -48932,7 +58381,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -49088,7 +58551,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "repeats": false @@ -49106,6 +58583,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -49128,7 +58606,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "304" } ], "repeats": false, @@ -49160,7 +58639,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false, @@ -49180,7 +58660,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false, @@ -49263,7 +58765,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -49291,7 +58807,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -49583,7 +59113,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -49862,7 +59406,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -49875,7 +59420,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -49886,11 +59445,40 @@ "tag": "GetLinkedGroupResponseClientError", "moduleName": "WASmaxInGroupsGetLinkedGroupResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 404, + 405 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "not-allowed" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -49903,7 +59491,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -49919,14 +59508,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -49938,6 +59529,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -49949,14 +59545,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -49968,6 +59566,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -49979,14 +59582,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -49998,6 +59603,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -50009,14 +59619,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -50028,6 +59640,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -50069,11 +59686,38 @@ "tag": "GetLinkedGroupResponseServerError", "moduleName": "WASmaxInGroupsGetLinkedGroupResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -50086,7 +59730,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -50102,14 +59747,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -50121,6 +59768,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -50132,14 +59784,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -50151,6 +59805,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -50162,14 +59821,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -50181,6 +59842,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -50253,7 +59919,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -50266,7 +59933,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -50318,6 +59999,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -50330,7 +60025,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -50343,7 +60039,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -50390,11 +60100,38 @@ "tag": "GetLinkedGroupsParticipantsResponseClientError", "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-authorized", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -50407,7 +60144,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -50423,14 +60161,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -50442,6 +60182,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -50453,14 +60198,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -50472,6 +60219,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -50483,14 +60235,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -50502,6 +60256,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -50513,14 +60272,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -50532,6 +60293,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -50546,11 +60312,38 @@ "tag": "GetLinkedGroupsParticipantsResponseServerError", "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -50563,7 +60356,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -50579,14 +60373,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -50598,6 +60394,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -50609,14 +60410,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -50628,6 +60431,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -50639,14 +60447,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -50658,6 +60468,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -50737,7 +60552,8 @@ "name": "requestorFetch", "wireName": "requestor_fetch", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "repeats": false @@ -50747,7 +60563,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -50760,7 +60577,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -50819,7 +60650,25 @@ "name": "requestMethod", "wireName": "request_method", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_INVITELINK_LINKEDGROUPJOIN_NONADMINADD", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "invite_link", + "value": "invite_link" + }, + { + "name": "linked_group_join", + "value": "linked_group_join" + }, + { + "name": "non_admin_add", + "value": "non_admin_add" + } + ] + } }, { "method": "", @@ -50898,6 +60747,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -50917,7 +60780,8 @@ "name": "requestorFetch", "wireName": "requestor_fetch", "type": "string", - "required": true + "required": true, + "literalValue": "true" } ], "repeats": false @@ -50927,7 +60791,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -50940,7 +60805,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -50999,7 +60878,25 @@ "name": "requestMethod", "wireName": "request_method", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_INVITELINK_LINKEDGROUPJOIN_NONADMINADD", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "invite_link", + "value": "invite_link" + }, + { + "name": "linked_group_join", + "value": "linked_group_join" + }, + { + "name": "non_admin_add", + "value": "non_admin_add" + } + ] + } }, { "method": "", @@ -51073,11 +60970,44 @@ "tag": "GetMembershipApprovalRequestsResponseClientError", "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -51090,7 +61020,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -51106,14 +61037,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -51125,6 +61058,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -51136,14 +61074,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -51155,6 +61095,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -51166,14 +61111,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -51185,6 +61132,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -51196,14 +61148,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -51215,6 +61169,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -51226,14 +61185,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -51245,6 +61206,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -51256,14 +61222,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -51275,6 +61243,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -51316,11 +61289,38 @@ "tag": "GetMembershipApprovalRequestsResponseServerError", "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -51333,7 +61333,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -51349,14 +61350,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -51368,6 +61371,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -51379,14 +61387,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -51398,6 +61408,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -51409,14 +61424,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -51428,6 +61445,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -51515,7 +61537,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -51542,7 +61565,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -51561,7 +61602,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -51639,7 +61681,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -52095,7 +62151,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -52155,7 +62212,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -52172,7 +62230,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -52206,7 +62286,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -52243,6 +62324,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -52389,7 +62471,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -52417,7 +62513,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -52709,7 +62819,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -52985,7 +63109,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -53016,7 +63141,8 @@ "name": "truncated", "wireName": "truncated", "type": "string", - "required": true + "required": true, + "literalValue": "true" }, { "method": "attrInt", @@ -53059,10 +63185,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -53071,7 +63211,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -53098,7 +63239,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "sameNode": true @@ -53117,7 +63276,8 @@ "name": "ack", "wireName": "ack", "type": "string", - "required": false + "required": false, + "literalValue": "false" }, { "method": "attrString", @@ -53195,7 +63355,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -53651,7 +63825,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -53711,7 +63886,8 @@ "name": "canAutoFile", "wireName": "can_auto_file", "type": "string", - "required": false + "required": false, + "literalValue": "true" } ], "repeats": false @@ -53728,7 +63904,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_APPROVED_INREVIEW_NONE_REJECTED", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "approved", + "value": "approved" + }, + { + "name": "in_review", + "value": "in_review" + }, + { + "name": "none", + "value": "none" + }, + { + "name": "rejected", + "value": "rejected" + } + ] + } } ], "repeats": false @@ -53762,7 +63960,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "invite" }, { "method": "attrInt", @@ -53799,6 +63998,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -53945,7 +64145,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrString", @@ -53973,7 +64187,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -54265,7 +64493,21 @@ "name": "groupHistorySent", "wireName": "group_history_sent", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "", @@ -54541,7 +64783,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -54572,7 +64815,8 @@ "name": "truncated", "wireName": "truncated", "type": "string", - "required": true + "required": true, + "literalValue": "true" }, { "method": "attrInt", @@ -54610,11 +64854,36 @@ "tag": "GetParticipatingGroupsResponseClientError", "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 429 + ], + "errorTexts": [ + "bad-request", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -54627,7 +64896,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -54643,14 +64913,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -54662,6 +64934,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -54673,14 +64950,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -54692,6 +64971,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -54733,11 +65017,38 @@ "tag": "GetParticipatingGroupsResponseServerError", "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -54750,7 +65061,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -54766,14 +65078,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -54785,6 +65099,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -54796,14 +65115,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -54815,6 +65136,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -54826,14 +65152,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -54845,6 +65173,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -54917,7 +65250,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -54930,7 +65264,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -55050,6 +65398,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55062,7 +65424,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -55075,7 +65438,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -55190,11 +65567,40 @@ "tag": "GetReportedMessagesResponseClientError", "moduleName": "WASmaxInGroupsGetReportedMessagesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "locked", + "not-authorized", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55207,7 +65613,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -55223,14 +65630,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -55242,6 +65651,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -55253,14 +65667,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -55272,6 +65688,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -55283,14 +65704,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -55302,6 +65725,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -55313,14 +65741,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -55332,6 +65762,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -55343,14 +65778,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -55362,6 +65799,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] } @@ -55376,11 +65818,38 @@ "tag": "GetReportedMessagesResponseServerError", "moduleName": "WASmaxInGroupsGetReportedMessagesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55393,7 +65862,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -55409,14 +65879,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -55428,6 +65900,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -55439,14 +65916,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -55458,6 +65937,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -55469,14 +65953,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -55488,6 +65974,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -55754,7 +66245,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -55767,6 +66259,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55779,7 +66285,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -55792,6 +66299,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55804,7 +66325,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -55812,11 +66334,55 @@ "tag": "AcceptGroupAddResponseClientError", "moduleName": "WASmaxInGroupsAcceptGroupAddResponseClientError", "kind": "error", + "errorCodes": [ + 304, + 400, + 401, + 403, + 404, + 405, + 409, + 410, + 412, + 419, + 423, + 500 + ], + "errorTexts": [ + "already-exists", + "bad-request", + "conflict", + "forbidden", + "gone", + "item-not-found", + "linked-groups-participants-resource-limit", + "locked", + "not-allowed", + "not-authorized", + "resource-constraint", + "resource-limit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -55829,7 +66395,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -55845,14 +66412,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "already-exists" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -55864,6 +66433,11 @@ "kind": "attr", "name": "text", "value": "already-exists" + }, + { + "kind": "attr", + "name": "code", + "value": "304" } ] }, @@ -55875,14 +66449,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -55894,6 +66470,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -55905,14 +66486,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -55924,6 +66507,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -55935,14 +66523,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -55954,6 +66544,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -55965,14 +66560,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -55984,6 +66581,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -55995,14 +66597,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -56014,6 +66618,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -56025,14 +66634,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "gone" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "410" } ], "assertions": [ @@ -56044,6 +66655,11 @@ "kind": "attr", "name": "text", "value": "gone" + }, + { + "kind": "attr", + "name": "code", + "value": "410" } ] }, @@ -56055,14 +66671,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -56074,6 +66692,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -56085,14 +66708,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -56104,6 +66729,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -56115,14 +66745,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-constraint" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -56134,6 +66766,11 @@ "kind": "attr", "name": "text", "value": "resource-constraint" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -56145,14 +66782,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -56164,6 +66803,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -56175,14 +66819,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "linked-groups-participants-resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "412" } ], "assertions": [ @@ -56194,6 +66840,11 @@ "kind": "attr", "name": "text", "value": "linked-groups-participants-resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "412" } ] }, @@ -56235,11 +66886,38 @@ "tag": "AcceptGroupAddResponseServerError", "moduleName": "WASmaxInGroupsAcceptGroupAddResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -56252,7 +66930,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -56268,14 +66947,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -56287,6 +66968,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -56298,14 +66984,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -56317,6 +67005,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -56328,14 +67021,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -56347,6 +67042,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -56419,7 +67119,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -56432,10 +67133,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -56444,7 +67159,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -56452,11 +67168,38 @@ "tag": "AcknowledgeGroupResponseClientError", "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -56469,7 +67212,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -56485,14 +67229,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -56504,6 +67250,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -56515,14 +67266,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -56534,6 +67287,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -56545,14 +67303,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -56564,6 +67324,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -56605,11 +67370,38 @@ "tag": "AcknowledgeGroupResponseServerError", "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -56622,7 +67414,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -56638,14 +67431,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -56657,6 +67452,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -56668,14 +67468,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -56687,6 +67489,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -56698,14 +67505,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -56717,6 +67526,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -56813,7 +67627,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -56826,7 +67641,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -56907,7 +67736,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "421" }, { "method": "maybeAttrEnum", @@ -56940,7 +67770,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -56963,7 +67794,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "402" } ], "assertions": [ @@ -56986,7 +67818,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -57019,7 +67852,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -57045,7 +67879,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -57068,7 +67903,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -57091,7 +67927,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -57114,7 +67951,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -57137,7 +67975,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -57160,7 +67999,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -57183,7 +68023,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -57206,7 +68047,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "assertions": [ @@ -57229,7 +68071,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "417" } ], "assertions": [ @@ -57252,7 +68095,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "431" } ], "assertions": [ @@ -57275,7 +68119,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "432" } ], "assertions": [ @@ -57324,7 +68169,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -57357,7 +68203,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -57383,7 +68230,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -57406,7 +68254,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -57450,10 +68299,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -57462,7 +68325,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -57475,7 +68339,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -57556,7 +68434,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "421" }, { "method": "maybeAttrEnum", @@ -57589,7 +68468,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -57612,7 +68492,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "402" } ], "assertions": [ @@ -57635,7 +68516,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -57668,7 +68550,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -57694,7 +68577,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -57717,7 +68601,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -57740,7 +68625,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -57763,7 +68649,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -57786,7 +68673,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -57809,7 +68697,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -57832,7 +68721,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -57855,7 +68745,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "assertions": [ @@ -57878,7 +68769,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "417" } ], "assertions": [ @@ -57901,7 +68793,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "431" } ], "assertions": [ @@ -57924,7 +68817,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "432" } ], "assertions": [ @@ -57973,7 +68867,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -58006,7 +68901,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -58032,7 +68928,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -58055,7 +68952,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -58094,11 +68992,48 @@ "tag": "AddParticipantsResponseClientError", "moduleName": "WASmaxInGroupsAddParticipantsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 405, + 416, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-allowed", + "not-authorized", + "parent-group-suspended", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -58111,7 +69046,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -58127,14 +69063,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -58146,6 +69084,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -58157,14 +69100,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -58176,6 +69121,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -58187,14 +69137,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -58206,6 +69158,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -58217,14 +69174,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -58236,6 +69195,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -58247,14 +69211,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" }, { "method": "", @@ -58277,7 +69243,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_GROUP_USER", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "group", + "value": "group" + }, + { + "name": "user", + "value": "user" + } + ] + } } ], "assertions": [ @@ -58322,6 +69302,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -58333,14 +69318,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -58352,6 +69339,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -58363,14 +69355,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -58382,6 +69376,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -58393,14 +69392,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -58412,6 +69413,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -58423,14 +69429,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "parent-group-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -58442,6 +69450,11 @@ "kind": "attr", "name": "text", "value": "parent-group-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] }, @@ -58483,11 +69496,38 @@ "tag": "AddParticipantsResponseServerError", "moduleName": "WASmaxInGroupsAddParticipantsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -58500,7 +69540,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -58516,14 +69557,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -58535,6 +69578,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -58546,14 +69594,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -58565,6 +69615,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -58576,14 +69631,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -58595,6 +69652,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -58681,7 +69743,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -58694,7 +69757,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -58743,7 +69820,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -58766,7 +69844,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -58800,6 +69879,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -58812,7 +69905,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -58825,7 +69919,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -58874,7 +69982,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -58897,7 +70006,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -58926,11 +70036,40 @@ "tag": "CancelGroupMembershipRequestsResponseClientError", "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "locked", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -58943,7 +70082,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -58959,14 +70099,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -58978,6 +70120,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -58989,14 +70136,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -59008,6 +70157,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -59019,14 +70173,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -59038,6 +70194,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -59049,14 +70210,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -59068,6 +70231,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -59109,11 +70277,38 @@ "tag": "CancelGroupMembershipRequestsResponseServerError", "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -59126,7 +70321,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -59142,14 +70338,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -59161,6 +70359,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -59172,14 +70375,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -59191,6 +70396,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -59202,14 +70412,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -59221,6 +70433,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -59502,7 +70719,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -59573,7 +70791,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "repeats": false @@ -59656,7 +70892,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "repeats": false @@ -59985,7 +71235,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -60046,6 +71297,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -60175,7 +71427,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "417" } ], "assertions": [ @@ -60198,7 +71451,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -60221,7 +71475,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -60254,7 +71509,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -60280,7 +71536,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -60303,7 +71560,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -60326,7 +71584,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -60349,7 +71608,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -60372,7 +71632,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -60395,7 +71656,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "431" } ], "assertions": [ @@ -60418,7 +71680,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "432" } ], "assertions": [ @@ -60441,7 +71704,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrInt", @@ -60461,7 +71738,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -60508,7 +71786,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -60541,7 +71820,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -60567,7 +71847,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -60590,7 +71871,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -60638,6 +71920,13 @@ "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -60653,7 +71942,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrString", @@ -60724,7 +72014,25 @@ "name": "createCtx", "wireName": "create_ctx", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_E2EEMIGRATION_REGULAR_RTC", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "e2ee_migration", + "value": "e2ee_migration" + }, + { + "name": "regular", + "value": "regular" + }, + { + "name": "rtc", + "value": "rtc" + } + ] + } } ], "repeats": false @@ -60807,7 +72115,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "repeats": false @@ -61136,7 +72458,8 @@ "name": "defaultMembershipApprovalMode", "wireName": "default_membership_approval_mode", "type": "string", - "required": true + "required": true, + "literalValue": "request_required" } ], "sameNode": true @@ -61197,6 +72520,7 @@ "wireName": "state", "type": "string", "required": true, + "literalValue": "on", "sourcePath": [ "group_join" ] @@ -61326,7 +72650,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "417" } ], "assertions": [ @@ -61349,7 +72674,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -61372,7 +72698,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -61405,7 +72732,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -61431,7 +72759,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -61454,7 +72783,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -61477,7 +72807,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -61500,7 +72831,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -61523,7 +72855,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -61546,7 +72879,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "431" } ], "assertions": [ @@ -61569,7 +72903,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "432" } ], "assertions": [ @@ -61592,7 +72927,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ADMIN_SUPERADMIN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "admin", + "value": "admin" + }, + { + "name": "superadmin", + "value": "superadmin" + } + ] + } }, { "method": "maybeAttrInt", @@ -61612,7 +72961,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -61659,7 +73009,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" }, { "method": "maybeAttrString", @@ -61692,7 +73043,8 @@ "name": "addressable", "wireName": "addressable", "type": "string", - "required": true + "required": true, + "literalValue": "false" } ], "sameNode": true @@ -61718,7 +73070,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "451" } ], "assertions": [ @@ -61741,7 +73094,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -61789,6 +73143,13 @@ "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -61804,7 +73165,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrGroupJid", @@ -61822,11 +73184,51 @@ "tag": "CreateResponseClientError", "moduleName": "WASmaxInGroupsCreateResponseClientError", "kind": "error", + "errorCodes": [ + 400, + 403, + 404, + 405, + 406, + 412, + 416, + 419, + 429, + 500 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "linked-groups-participants-resource-limit", + "not-acceptable", + "not-allowed", + "parent-group-suspended", + "rate-overlimit", + "resource-constraint", + "resource-limit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -61839,7 +73241,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -61855,14 +73258,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -61874,6 +73279,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -61885,14 +73295,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-constraint" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -61904,6 +73316,11 @@ "kind": "attr", "name": "text", "value": "resource-constraint" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -61915,14 +73332,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -61934,6 +73353,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -61945,14 +73369,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -61964,6 +73390,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -61975,14 +73406,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -62018,6 +73451,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -62029,14 +73467,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -62048,6 +73488,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -62059,14 +73504,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" }, { "method": "", @@ -62089,7 +73536,21 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_GROUP_USER", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "group", + "value": "group" + }, + { + "name": "user", + "value": "user" + } + ] + } } ], "assertions": [ @@ -62134,6 +73595,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -62145,14 +73611,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -62164,6 +73632,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -62175,14 +73648,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -62194,6 +73669,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -62205,14 +73685,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "linked-groups-participants-resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "412" } ], "assertions": [ @@ -62224,6 +73706,11 @@ "kind": "attr", "name": "text", "value": "linked-groups-participants-resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "412" } ] }, @@ -62235,14 +73722,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "parent-group-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -62254,6 +73743,11 @@ "kind": "attr", "name": "text", "value": "parent-group-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] }, @@ -62295,11 +73789,38 @@ "tag": "CreateResponseServerError", "moduleName": "WASmaxInGroupsCreateResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -62312,7 +73833,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -62328,14 +73850,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -62347,6 +73871,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -62358,14 +73887,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -62377,6 +73908,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -62388,14 +73924,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -62407,6 +73945,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -62526,7 +74069,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -62539,7 +74083,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -62556,7 +74114,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "406" } ], "repeats": false, @@ -62575,6 +74134,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -62634,7 +74207,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -62647,7 +74221,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -62664,7 +74252,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "406" } ], "repeats": false, @@ -62683,6 +74272,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -62695,7 +74298,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -62708,7 +74312,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -62741,7 +74359,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -62764,7 +74383,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -62787,7 +74407,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -62810,7 +74431,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -62833,7 +74455,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -62856,7 +74479,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -62879,7 +74503,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -62902,7 +74527,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -62931,11 +74557,46 @@ "tag": "CreateSubGroupSuggestionResponseClientError", "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 404, + 405, + 406, + 419, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "not-acceptable", + "not-allowed", + "rate-overlimit", + "resource-limit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -62948,7 +74609,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -62964,14 +74626,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -62983,6 +74647,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -62994,14 +74663,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -63013,6 +74684,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -63024,14 +74700,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -63043,6 +74721,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -63054,14 +74737,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -63097,6 +74782,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -63108,14 +74798,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -63127,6 +74819,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -63138,14 +74835,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -63157,6 +74856,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -63168,14 +74872,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -63187,6 +74893,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -63228,11 +74939,38 @@ "tag": "CreateSubGroupSuggestionResponseServerError", "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63245,7 +74983,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -63261,14 +75000,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -63280,6 +75021,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -63291,14 +75037,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -63310,6 +75058,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -63321,14 +75074,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -63340,6 +75095,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -63412,7 +75172,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -63425,6 +75186,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63437,7 +75212,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -63445,11 +75221,36 @@ "tag": "DeleteParentGroupResponseClientError", "moduleName": "WASmaxInGroupsDeleteParentGroupResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-authorized" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63462,7 +75263,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -63478,14 +75280,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -63497,6 +75301,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -63508,14 +75317,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -63527,6 +75338,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -63538,14 +75354,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -63557,6 +75375,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] } @@ -63571,11 +75394,38 @@ "tag": "DeleteParentGroupResponseServerError", "moduleName": "WASmaxInGroupsDeleteParentGroupResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63588,7 +75438,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -63604,14 +75455,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -63623,6 +75476,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -63634,14 +75492,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -63653,6 +75513,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -63664,14 +75529,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -63683,6 +75550,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -63766,7 +75638,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -63779,6 +75652,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63791,7 +75678,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -63804,6 +75692,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63816,7 +75718,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -63824,11 +75727,51 @@ "tag": "JoinLinkedGroupResponseClientError", "moduleName": "WASmaxInGroupsJoinLinkedGroupResponseClientError", "kind": "error", + "errorCodes": [ + 304, + 400, + 401, + 403, + 404, + 405, + 406, + 409, + 419, + 426 + ], + "errorTexts": [ + "already-exists", + "bad-request", + "conflict", + "forbidden", + "item-not-found", + "not-acceptable", + "not-allowed", + "not-authorized", + "resource-limit", + "upgrade-required" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -63841,7 +75784,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -63857,14 +75801,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -63876,6 +75822,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -63887,14 +75838,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -63906,6 +75859,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -63917,14 +75875,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -63936,6 +75896,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -63947,14 +75912,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -63966,6 +75933,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -63977,14 +75949,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -64020,6 +75994,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -64031,14 +76010,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -64050,6 +76031,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -64061,14 +76047,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -64080,6 +76068,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -64091,14 +76084,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -64110,6 +76105,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -64121,14 +76121,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "upgrade-required" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "426" } ], "assertions": [ @@ -64140,6 +76142,11 @@ "kind": "attr", "name": "text", "value": "upgrade-required" + }, + { + "kind": "attr", + "name": "code", + "value": "426" } ] }, @@ -64151,14 +76158,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "already-exists" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -64170,6 +76179,11 @@ "kind": "attr", "name": "text", "value": "already-exists" + }, + { + "kind": "attr", + "name": "code", + "value": "304" } ] }, @@ -64211,11 +76225,38 @@ "tag": "JoinLinkedGroupResponseServerError", "moduleName": "WASmaxInGroupsJoinLinkedGroupResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -64228,7 +76269,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -64244,14 +76286,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -64263,6 +76307,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -64274,14 +76323,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -64293,6 +76344,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -64304,14 +76360,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -64323,6 +76381,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -64432,6 +76495,7 @@ "wireName": "link_type", "type": "string", "required": true, + "literalValue": "sub_group", "sourcePath": [ "links", "link" @@ -64442,7 +76506,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -64455,7 +76520,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -64488,7 +76567,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -64511,7 +76591,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -64534,7 +76615,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -64557,7 +76639,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -64580,7 +76663,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -64603,7 +76687,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -64626,7 +76711,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "433" } ], "assertions": [ @@ -64649,7 +76735,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -64691,7 +76778,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "repeats": true @@ -64714,6 +76802,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -64727,6 +76829,7 @@ "wireName": "link_type", "type": "string", "required": true, + "literalValue": "sub_group", "sourcePath": [ "links", "link" @@ -64737,7 +76840,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -64750,7 +76854,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -64783,7 +76901,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -64806,7 +76925,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -64829,7 +76949,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -64852,7 +76973,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -64875,7 +76997,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -64898,7 +77021,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -64921,7 +77045,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "433" } ], "assertions": [ @@ -64944,7 +77069,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -64986,7 +77112,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "repeats": true @@ -65004,11 +77131,50 @@ "tag": "LinkSubGroupsResponseClientError", "moduleName": "WASmaxInGroupsLinkSubGroupsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 405, + 413, + 416, + 419, + 429, + 433 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "not-allowed", + "not-authorized", + "parent-group-suspended", + "payload-too-large", + "pending-add-requests", + "rate-overlimit", + "resource-limit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -65021,7 +77187,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -65037,14 +77204,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -65056,6 +77225,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -65067,14 +77241,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -65086,6 +77262,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -65097,14 +77278,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -65116,6 +77299,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -65127,14 +77315,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -65146,6 +77336,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -65157,14 +77352,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -65176,6 +77373,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -65187,14 +77389,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-too-large" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "413" } ], "assertions": [ @@ -65206,6 +77410,11 @@ "kind": "attr", "name": "text", "value": "payload-too-large" + }, + { + "kind": "attr", + "name": "code", + "value": "413" } ] }, @@ -65217,14 +77426,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -65236,6 +77447,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -65247,14 +77463,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "parent-group-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -65266,6 +77484,11 @@ "kind": "attr", "name": "text", "value": "parent-group-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] }, @@ -65277,14 +77500,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "pending-add-requests" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "433" } ], "assertions": [ @@ -65296,6 +77521,11 @@ "kind": "attr", "name": "text", "value": "pending-add-requests" + }, + { + "kind": "attr", + "name": "code", + "value": "433" } ] }, @@ -65307,14 +77537,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -65326,6 +77558,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] } @@ -65340,11 +77577,38 @@ "tag": "LinkSubGroupsResponseServerError", "moduleName": "WASmaxInGroupsLinkSubGroupsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -65357,7 +77621,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -65373,14 +77638,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -65392,6 +77659,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -65403,14 +77675,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -65422,6 +77696,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -65433,14 +77712,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -65452,6 +77733,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -65587,7 +77873,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -65600,7 +77887,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -65700,7 +78001,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -65723,7 +78025,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -65746,7 +78049,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -65769,7 +78073,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -65792,7 +78097,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -65815,7 +78121,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -65838,7 +78145,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -65861,7 +78169,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "412" } ], "assertions": [ @@ -65982,7 +78291,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -66005,7 +78315,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -66042,10 +78353,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -66054,7 +78379,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -66067,7 +78393,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -66167,7 +78507,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -66190,7 +78531,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -66213,7 +78555,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -66236,7 +78579,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -66259,7 +78603,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -66282,7 +78627,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -66305,7 +78651,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -66328,7 +78675,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "412" } ], "assertions": [ @@ -66449,7 +78797,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -66472,7 +78821,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -66504,11 +78854,42 @@ "tag": "MembershipRequestsActionResponseClientError", "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "locked", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -66521,7 +78902,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -66537,14 +78919,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -66556,6 +78940,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -66567,14 +78956,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -66586,6 +78977,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -66597,14 +78993,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -66616,6 +79014,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -66627,14 +79030,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -66646,6 +79051,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -66657,14 +79067,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -66676,6 +79088,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -66717,11 +79134,38 @@ "tag": "MembershipRequestsActionResponseServerError", "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -66734,7 +79178,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -66750,14 +79195,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -66769,6 +79216,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -66780,14 +79232,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -66799,6 +79253,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -66810,14 +79269,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -66829,6 +79290,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -66944,7 +79410,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -66957,7 +79424,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -66981,7 +79462,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "admin" }, { "method": "maybeAttrEnum", @@ -67039,6 +79521,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67051,7 +79547,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -67064,7 +79561,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -67088,7 +79599,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "admin" }, { "method": "maybeAttrEnum", @@ -67141,11 +79653,46 @@ "tag": "PromoteDemoteAdminResponseClientError", "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 423, + 429, + 486 + ], + "errorTexts": [ + "bad-request", + "blocked-integrity-enforcement", + "forbidden", + "item-not-found", + "locked", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67158,7 +79705,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -67174,14 +79722,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -67193,6 +79743,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -67204,14 +79759,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "blocked-integrity-enforcement" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "486" } ], "assertions": [ @@ -67223,6 +79780,11 @@ "kind": "attr", "name": "text", "value": "blocked-integrity-enforcement" + }, + { + "kind": "attr", + "name": "code", + "value": "486" } ] }, @@ -67234,14 +79796,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -67253,6 +79817,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -67264,14 +79833,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -67283,6 +79854,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -67294,14 +79870,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -67313,6 +79891,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -67324,14 +79907,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -67343,6 +79928,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -67354,14 +79944,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -67373,6 +79965,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -67414,11 +80011,38 @@ "tag": "PromoteDemoteAdminResponseServerError", "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67431,7 +80055,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -67447,14 +80072,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -67466,6 +80093,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -67477,14 +80109,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -67496,6 +80130,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -67507,14 +80146,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -67526,6 +80167,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -67634,7 +80280,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -67647,7 +80294,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -67671,7 +80332,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "admin" }, { "method": "maybeAttrEnum", @@ -67729,6 +80391,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67741,7 +80417,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -67754,7 +80431,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -67778,7 +80469,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "admin" }, { "method": "maybeAttrEnum", @@ -67836,6 +80528,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67848,7 +80554,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -67861,7 +80568,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -67931,11 +80652,46 @@ "tag": "PromoteDemoteResponseClientError", "moduleName": "WASmaxInGroupsPromoteDemoteResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 423, + 429, + 486 + ], + "errorTexts": [ + "bad-request", + "blocked-integrity-enforcement", + "forbidden", + "item-not-found", + "locked", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -67948,7 +80704,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -67964,14 +80721,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -67983,6 +80742,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -67994,14 +80758,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "blocked-integrity-enforcement" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "486" } ], "assertions": [ @@ -68013,6 +80779,11 @@ "kind": "attr", "name": "text", "value": "blocked-integrity-enforcement" + }, + { + "kind": "attr", + "name": "code", + "value": "486" } ] }, @@ -68024,14 +80795,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -68043,6 +80816,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -68054,14 +80832,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -68073,6 +80853,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -68084,14 +80869,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -68103,6 +80890,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -68114,14 +80906,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -68133,6 +80927,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -68144,14 +80943,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -68163,6 +80964,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -68204,11 +81010,38 @@ "tag": "PromoteDemoteResponseServerError", "moduleName": "WASmaxInGroupsPromoteDemoteResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -68221,7 +81054,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -68237,14 +81071,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -68256,6 +81092,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -68267,14 +81108,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -68286,6 +81129,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -68297,14 +81145,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -68316,6 +81166,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -68408,7 +81263,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -68416,6 +81272,7 @@ "wireName": "linked_groups", "type": "string", "required": false, + "literalValue": "true", "sourcePath": [ "remove" ] @@ -68431,7 +81288,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -68496,7 +81367,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -68519,7 +81391,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -68542,7 +81415,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -68565,7 +81439,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -68599,10 +81474,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -68611,7 +81500,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "maybeAttrString", @@ -68619,6 +81509,7 @@ "wireName": "linked_groups", "type": "string", "required": false, + "literalValue": "true", "sourcePath": [ "remove" ] @@ -68634,7 +81525,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -68699,7 +81604,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -68722,7 +81628,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -68745,7 +81652,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -68768,7 +81676,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -68797,11 +81706,46 @@ "tag": "RemoveParticipantsResponseClientError", "moduleName": "WASmaxInGroupsRemoveParticipantsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 413, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-authorized", + "payload-too-large", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -68814,7 +81758,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -68830,14 +81775,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -68849,6 +81796,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -68860,14 +81812,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-too-large" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "413" } ], "assertions": [ @@ -68879,6 +81833,11 @@ "kind": "attr", "name": "text", "value": "payload-too-large" + }, + { + "kind": "attr", + "name": "code", + "value": "413" } ] }, @@ -68890,14 +81849,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -68909,6 +81870,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -68920,14 +81886,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -68939,6 +81907,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -68950,14 +81923,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -68969,6 +81944,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -68980,14 +81960,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -68999,6 +81981,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -69010,14 +81997,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -69029,6 +82018,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -69070,11 +82064,38 @@ "tag": "RemoveParticipantsResponseServerError", "moduleName": "WASmaxInGroupsRemoveParticipantsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69087,7 +82108,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -69103,14 +82125,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -69122,6 +82146,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -69133,14 +82162,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -69152,6 +82183,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -69163,14 +82199,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -69182,6 +82220,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -69267,7 +82310,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -69280,6 +82324,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69292,7 +82350,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -69300,11 +82359,40 @@ "tag": "ReportMessagesResponseClientError", "moduleName": "WASmaxInGroupsReportMessagesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 403, + 404, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69317,7 +82405,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -69333,14 +82422,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -69352,6 +82443,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -69363,14 +82459,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -69382,6 +82480,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -69393,14 +82496,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -69412,6 +82517,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -69423,14 +82533,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -69442,6 +82554,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -69453,14 +82570,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -69472,6 +82591,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] } @@ -69486,11 +82610,38 @@ "tag": "ReportMessagesResponseServerError", "moduleName": "WASmaxInGroupsReportMessagesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69503,7 +82654,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -69519,14 +82671,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -69538,6 +82692,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -69549,14 +82708,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -69568,6 +82729,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -69579,14 +82745,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -69598,6 +82766,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -69684,7 +82857,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -69697,7 +82871,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -69721,7 +82909,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "404" }, { "method": "", @@ -69772,6 +82961,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69784,7 +82987,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -69797,7 +83001,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -69821,7 +83039,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": false + "required": false, + "literalValue": "404" }, { "method": "", @@ -69867,11 +83086,42 @@ "tag": "RevokeRequestCodeResponseClientError", "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 423 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-authorized" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -69884,7 +83134,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -69900,14 +83151,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -69919,6 +83172,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -69930,14 +83188,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -69949,6 +83209,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -69960,14 +83225,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -69979,6 +83246,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -69990,14 +83262,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -70009,6 +83283,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -70020,14 +83299,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -70039,6 +83320,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -70080,11 +83366,38 @@ "tag": "RevokeRequestCodeResponseServerError", "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -70097,7 +83410,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -70113,14 +83427,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -70132,6 +83448,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -70143,14 +83464,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -70162,6 +83485,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -70173,14 +83501,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -70192,6 +83522,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -70295,7 +83630,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -70308,6 +83644,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -70327,7 +83677,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -70335,11 +83686,46 @@ "tag": "SetDescriptionResponseClientError", "moduleName": "WASmaxInGroupsSetDescriptionResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 406, + 409, + 423 + ], + "errorTexts": [ + "bad-request", + "conflict", + "forbidden", + "item-not-found", + "locked", + "not-acceptable", + "not-authorized" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -70352,7 +83738,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -70368,14 +83755,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -70387,6 +83776,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -70398,14 +83792,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -70417,6 +83813,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -70428,14 +83829,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -70447,6 +83850,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -70458,14 +83866,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -70477,6 +83887,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -70488,14 +83903,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -70507,6 +83924,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -70518,14 +83940,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -70561,6 +83985,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -70572,14 +84001,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -70591,6 +84022,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -70632,11 +84068,38 @@ "tag": "SetDescriptionResponseServerError", "moduleName": "WASmaxInGroupsSetDescriptionResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -70649,7 +84112,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -70665,14 +84129,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -70684,6 +84150,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -70695,14 +84166,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -70714,6 +84187,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -70725,14 +84203,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -70744,6 +84224,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -70932,7 +84417,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -71031,7 +84517,8 @@ "name": "state", "wireName": "state", "type": "string", - "required": true + "required": true, + "literalValue": "on" } ], "assertions": [ @@ -71054,7 +84541,8 @@ "name": "state", "wireName": "state", "type": "string", - "required": true + "required": true, + "literalValue": "off" } ], "assertions": [ @@ -71127,6 +84615,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -71139,7 +84641,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -71238,7 +84741,8 @@ "name": "state", "wireName": "state", "type": "string", - "required": true + "required": true, + "literalValue": "on" } ], "assertions": [ @@ -71261,7 +84765,8 @@ "name": "state", "wireName": "state", "type": "string", - "required": true + "required": true, + "literalValue": "off" } ], "assertions": [ @@ -71329,11 +84834,50 @@ "tag": "SetPropertyResponseClientError", "moduleName": "WASmaxInGroupsSetPropertyResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 405, + 419, + 423, + 426, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-allowed", + "not-authorized", + "rate-overlimit", + "resource-limit", + "upgrade-required" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -71346,7 +84890,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -71362,14 +84907,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -71381,6 +84928,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -71392,14 +84944,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -71411,6 +84965,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -71422,14 +84981,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -71441,6 +85002,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -71452,14 +85018,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -71471,6 +85039,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -71482,14 +85055,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -71501,6 +85076,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -71512,14 +85092,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "resource-limit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -71531,6 +85113,11 @@ "kind": "attr", "name": "text", "value": "resource-limit" + }, + { + "kind": "attr", + "name": "code", + "value": "419" } ] }, @@ -71542,14 +85129,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -71561,6 +85150,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -71572,14 +85166,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -71591,6 +85187,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -71602,14 +85203,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "upgrade-required" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "426" } ], "assertions": [ @@ -71621,6 +85224,11 @@ "kind": "attr", "name": "text", "value": "upgrade-required" + }, + { + "kind": "attr", + "name": "code", + "value": "426" } ] }, @@ -71662,11 +85270,38 @@ "tag": "SetPropertyResponseServerError", "moduleName": "WASmaxInGroupsSetPropertyResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -71679,7 +85314,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -71695,14 +85331,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -71714,6 +85352,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -71725,14 +85368,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -71744,6 +85389,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -71755,14 +85405,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -71774,6 +85426,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -71846,7 +85503,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -71859,6 +85517,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -71871,7 +85543,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -71879,11 +85552,46 @@ "tag": "SetSubjectResponseClientError", "moduleName": "WASmaxInGroupsSetSubjectResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 403, + 404, + 406, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "forbidden", + "item-not-found", + "locked", + "not-acceptable", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -71896,7 +85604,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -71912,14 +85621,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -71931,6 +85642,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -71942,14 +85658,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -71961,6 +85679,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -71972,14 +85695,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -71991,6 +85716,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -72002,14 +85732,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "forbidden" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "403" } ], "assertions": [ @@ -72021,6 +85753,11 @@ "kind": "attr", "name": "text", "value": "forbidden" + }, + { + "kind": "attr", + "name": "code", + "value": "403" } ] }, @@ -72032,14 +85769,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -72075,6 +85814,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -72086,14 +85830,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "locked" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "423" } ], "assertions": [ @@ -72105,6 +85851,11 @@ "kind": "attr", "name": "text", "value": "locked" + }, + { + "kind": "attr", + "name": "code", + "value": "423" } ] }, @@ -72116,14 +85867,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -72135,6 +85888,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -72176,11 +85934,38 @@ "tag": "SetSubjectResponseServerError", "moduleName": "WASmaxInGroupsSetSubjectResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -72193,7 +85978,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -72209,14 +85995,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -72228,6 +86016,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -72239,14 +86032,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -72258,6 +86053,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -72269,14 +86069,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -72288,6 +86090,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -72406,7 +86213,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -72419,7 +86227,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -72482,7 +86304,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -72505,7 +86328,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -72528,7 +86352,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -72551,7 +86376,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "433" } ], "assertions": [ @@ -72574,7 +86400,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -72655,7 +86482,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "sameNode": true @@ -72761,7 +86589,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "sameNode": true @@ -72846,6 +86675,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -72858,7 +86701,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "", @@ -72871,7 +86715,21 @@ "name": "addressingMode", "wireName": "addressing_mode", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_LID_PN", + "module": "WASmaxInGroupsEnums", + "variants": [ + { + "name": "lid", + "value": "lid" + }, + { + "name": "pn", + "value": "pn" + } + ] + } } ], "sameNode": true @@ -72934,7 +86792,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -72957,7 +86816,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -72980,7 +86840,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "419" } ], "assertions": [ @@ -73003,7 +86864,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "433" } ], "assertions": [ @@ -73026,7 +86888,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -73107,7 +86970,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "sameNode": true @@ -73213,7 +87077,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "sameNode": true @@ -73293,11 +87158,40 @@ "tag": "SubGroupSuggestionsActionResponseClientError", "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-authorized", + "rate-overlimit" + ], + "errorCodeMin": 400, + "errorCodeMax": 499, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -73310,7 +87204,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -73326,14 +87221,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -73345,6 +87242,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -73356,14 +87258,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -73375,6 +87279,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -73386,14 +87295,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -73405,6 +87316,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -73416,14 +87332,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -73435,6 +87353,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -73476,11 +87399,38 @@ "tag": "SubGroupSuggestionsActionResponseServerError", "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -73493,7 +87443,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -73509,14 +87460,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -73528,6 +87481,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -73539,14 +87497,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -73558,6 +87518,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -73569,14 +87534,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -73588,6 +87555,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -73687,6 +87659,7 @@ "wireName": "unlink_type", "type": "string", "required": true, + "literalValue": "sub_group", "sourcePath": [ "unlink" ] @@ -73696,7 +87669,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -73717,7 +87691,8 @@ "name": "removeOrphanedMembers", "wireName": "remove_orphaned_members", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -73733,7 +87708,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -73756,7 +87732,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -73779,7 +87756,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -73802,7 +87780,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -73825,7 +87804,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -73848,7 +87828,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -73882,6 +87863,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -73895,6 +87890,7 @@ "wireName": "unlink_type", "type": "string", "required": true, + "literalValue": "sub_group", "sourcePath": [ "unlink" ] @@ -73904,7 +87900,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -73925,7 +87922,8 @@ "name": "removeOrphanedMembers", "wireName": "remove_orphaned_members", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -73941,7 +87939,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -73964,7 +87963,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -73987,7 +87987,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -74010,7 +88011,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "406" } ], "assertions": [ @@ -74033,7 +88035,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -74056,7 +88059,8 @@ "name": "error", "wireName": "error", "type": "string", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -74085,11 +88089,38 @@ "tag": "UnlinkGroupsResponseClientError", "moduleName": "WASmaxInGroupsUnlinkGroupsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 406 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-acceptable", + "not-authorized" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -74102,7 +88133,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -74118,14 +88150,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -74137,6 +88171,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -74148,14 +88187,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -74167,6 +88208,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -74178,14 +88224,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -74197,6 +88245,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -74208,14 +88261,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -74251,6 +88306,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] } @@ -74265,11 +88325,38 @@ "tag": "UnlinkGroupsResponseServerError", "moduleName": "WASmaxInGroupsUnlinkGroupsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500, + 503, + 530 + ], + "errorTexts": [ + "internal-server-error", + "partial-server-error", + "service-unavailable" + ], + "errorCodeMin": 500, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -74282,7 +88369,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -74298,14 +88386,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -74317,6 +88407,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -74328,14 +88423,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -74347,6 +88444,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -74358,14 +88460,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "partial-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "530" } ], "assertions": [ @@ -74377,6 +88481,11 @@ "kind": "attr", "name": "text", "value": "partial-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "530" } ] }, @@ -74657,6 +88766,13 @@ "kind": "attr", "name": "type", "value": "result" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -74672,7 +88788,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "attrInt", @@ -74764,14 +88881,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYONDELIVERY_PIXKEY", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "pay_on_delivery", + "value": "pay_on_delivery" + }, + { + "name": "pix_key", + "value": "pix_key" + } + ] + } }, { "method": "maybeAttrString", "name": "country", "wireName": "country", "type": "string", - "required": false + "required": false, + "literalValue": "BR" }, { "method": "maybeAttrString", @@ -74785,7 +88917,21 @@ "name": "flow", "wireName": "flow", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_P2M_P2P", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "p2m", + "value": "p2m" + }, + { + "name": "p2p", + "value": "p2p" + } + ] + } }, { "method": "attrString", @@ -74855,7 +89001,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -74868,6 +89015,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -74887,14 +89048,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYONDELIVERY_PIXKEY", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "pay_on_delivery", + "value": "pay_on_delivery" + }, + { + "name": "pix_key", + "value": "pix_key" + } + ] + } }, { "method": "maybeAttrString", "name": "country", "wireName": "country", "type": "string", - "required": false + "required": false, + "literalValue": "BR" }, { "method": "maybeAttrString", @@ -74908,7 +89084,21 @@ "name": "flow", "wireName": "flow", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_P2M_P2P", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "p2m", + "value": "p2m" + }, + { + "name": "p2p", + "value": "p2p" + } + ] + } }, { "method": "attrString", @@ -74978,7 +89168,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -74991,10 +89182,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "error" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -75003,7 +89208,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "attrString", @@ -75145,7 +89351,37 @@ "name": "state", "wireName": "state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_DELETED_INACTIVE_NEEDSRETOKENIZATION_NEEDSRETOKENIZATIONDELETED_SUSPENDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "DELETED", + "value": "DELETED" + }, + { + "name": "INACTIVE", + "value": "INACTIVE" + }, + { + "name": "NEEDS_RETOKENIZATION", + "value": "NEEDS_RETOKENIZATION" + }, + { + "name": "NEEDS_RETOKENIZATION_DELETED", + "value": "NEEDS_RETOKENIZATION_DELETED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + } + ] + } }, { "method": "maybeAttrString", @@ -75208,7 +89444,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -75343,6 +89601,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75353,6 +89629,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75363,6 +89657,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75373,6 +89685,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75464,7 +89794,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -75599,6 +89951,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75609,6 +89979,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75619,6 +90007,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75629,6 +90035,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75684,7 +90108,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -75819,6 +90265,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75829,6 +90293,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75839,6 +90321,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75849,6 +90349,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -75878,7 +90396,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "upi" }, { "method": "maybeAttrString", @@ -76146,7 +90665,49 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ACCOUNTPENDINGLINKING_ACTIVE_EXTERNALLYDISABLED_HARDBLOCKED_INACTIVE_INITED_INTEGRITYBLOCKED_PENDING_SOFTBLOCKED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACCOUNT_PENDING_LINKING", + "value": "ACCOUNT_PENDING_LINKING" + }, + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXTERNALLY_DISABLED", + "value": "EXTERNALLY_DISABLED" + }, + { + "name": "HARD_BLOCKED", + "value": "HARD_BLOCKED" + }, + { + "name": "INACTIVE", + "value": "INACTIVE" + }, + { + "name": "INITED", + "value": "INITED" + }, + { + "name": "INTEGRITY_BLOCKED", + "value": "INTEGRITY_BLOCKED" + }, + { + "name": "PENDING", + "value": "PENDING" + }, + { + "name": "SOFT_BLOCKED", + "value": "SOFT_BLOCKED" + } + ] + } }, { "method": "maybeAttrString", @@ -76283,7 +90844,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "bank" }, { "method": "maybeAttrString", @@ -76311,7 +90873,25 @@ "name": "verificationStatus", "wireName": "verification-status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAILED_PENDING_VERIFIED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "FAILED", + "value": "FAILED" + }, + { + "name": "PENDING", + "value": "PENDING" + }, + { + "name": "VERIFIED", + "value": "VERIFIED" + } + ] + } }, { "method": "attrString", @@ -76369,7 +90949,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "prepaid-card" }, { "method": "attrString", @@ -76467,14 +91048,86 @@ "name": "aliasType", "wireName": "alias_type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_MOBILENUMBER_NUMERICID", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "mobile_number", + "value": "mobile_number" + }, + { + "name": "numeric_id", + "value": "numeric_id" + } + ] + } }, { "method": "maybeAttrEnum", "name": "aliasStatus", "wireName": "alias_status", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_ACTIVEPENDING_ACTIVESTATUSPENDING_AVAILABLE_AVAILABLEPENDING_AVAILABLESTATUSPENDING_BLOCKED_DEREGISTERED_DEREGISTEREDPENDING_INACTIVE_INACTIVEPENDING_INACTIVESTATUSPENDING_UNKNOWN", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "active", + "value": "active" + }, + { + "name": "active_pending", + "value": "active_pending" + }, + { + "name": "active_status_pending", + "value": "active_status_pending" + }, + { + "name": "available", + "value": "available" + }, + { + "name": "available_pending", + "value": "available_pending" + }, + { + "name": "available_status_pending", + "value": "available_status_pending" + }, + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "deregistered", + "value": "deregistered" + }, + { + "name": "deregistered_pending", + "value": "deregistered_pending" + }, + { + "name": "inactive", + "value": "inactive" + }, + { + "name": "inactive_pending", + "value": "inactive_pending" + }, + { + "name": "inactive_status_pending", + "value": "inactive_status_pending" + }, + { + "name": "unknown", + "value": "unknown" + } + ] + } } ], "repeats": true @@ -76491,14 +91144,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYONDELIVERY_PIXKEY", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "pay_on_delivery", + "value": "pay_on_delivery" + }, + { + "name": "pix_key", + "value": "pix_key" + } + ] + } }, { "method": "maybeAttrString", "name": "country", "wireName": "country", "type": "string", - "required": false + "required": false, + "literalValue": "BR" }, { "method": "maybeAttrString", @@ -76512,7 +91180,21 @@ "name": "flow", "wireName": "flow", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_P2M_P2P", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "p2m", + "value": "p2m" + }, + { + "name": "p2p", + "value": "p2p" + } + ] + } }, { "method": "attrString", @@ -76582,7 +91264,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -76595,6 +91278,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -76672,7 +91369,37 @@ "name": "state", "wireName": "state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_DELETED_INACTIVE_NEEDSRETOKENIZATION_NEEDSRETOKENIZATIONDELETED_SUSPENDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "DELETED", + "value": "DELETED" + }, + { + "name": "INACTIVE", + "value": "INACTIVE" + }, + { + "name": "NEEDS_RETOKENIZATION", + "value": "NEEDS_RETOKENIZATION" + }, + { + "name": "NEEDS_RETOKENIZATION_DELETED", + "value": "NEEDS_RETOKENIZATION_DELETED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + } + ] + } }, { "method": "maybeAttrString", @@ -76735,7 +91462,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -76870,6 +91619,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -76880,6 +91647,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -76890,6 +91675,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -76900,6 +91703,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -76991,7 +91812,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -77126,6 +91969,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77136,6 +91997,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77146,6 +92025,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77156,6 +92053,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77211,7 +92126,29 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_EXPIRED_SUSPENDED_VOIDED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXPIRED", + "value": "EXPIRED" + }, + { + "name": "SUSPENDED", + "value": "SUSPENDED" + }, + { + "name": "VOIDED", + "value": "VOIDED" + } + ] + } }, { "method": "attrEnum", @@ -77346,6 +92283,24 @@ "wireName": "p2p-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77356,6 +92311,24 @@ "wireName": "p2p-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77366,6 +92339,24 @@ "wireName": "p2m-send", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77376,6 +92367,24 @@ "wireName": "p2m-receive", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_DISABLED_ENABLED_REQUIRESVERIFICATION", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "DISABLED", + "value": "DISABLED" + }, + { + "name": "ENABLED", + "value": "ENABLED" + }, + { + "name": "REQUIRES_VERIFICATION", + "value": "REQUIRES_VERIFICATION" + } + ] + }, "sourcePath": [ "capabilities" ] @@ -77405,7 +92414,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "upi" }, { "method": "maybeAttrString", @@ -77673,7 +92683,49 @@ "name": "displayState", "wireName": "display-state", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_ACCOUNTPENDINGLINKING_ACTIVE_EXTERNALLYDISABLED_HARDBLOCKED_INACTIVE_INITED_INTEGRITYBLOCKED_PENDING_SOFTBLOCKED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "ACCOUNT_PENDING_LINKING", + "value": "ACCOUNT_PENDING_LINKING" + }, + { + "name": "ACTIVE", + "value": "ACTIVE" + }, + { + "name": "EXTERNALLY_DISABLED", + "value": "EXTERNALLY_DISABLED" + }, + { + "name": "HARD_BLOCKED", + "value": "HARD_BLOCKED" + }, + { + "name": "INACTIVE", + "value": "INACTIVE" + }, + { + "name": "INITED", + "value": "INITED" + }, + { + "name": "INTEGRITY_BLOCKED", + "value": "INTEGRITY_BLOCKED" + }, + { + "name": "PENDING", + "value": "PENDING" + }, + { + "name": "SOFT_BLOCKED", + "value": "SOFT_BLOCKED" + } + ] + } }, { "method": "maybeAttrString", @@ -77810,7 +92862,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "bank" }, { "method": "maybeAttrString", @@ -77838,7 +92891,25 @@ "name": "verificationStatus", "wireName": "verification-status", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FAILED_PENDING_VERIFIED", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "FAILED", + "value": "FAILED" + }, + { + "name": "PENDING", + "value": "PENDING" + }, + { + "name": "VERIFIED", + "value": "VERIFIED" + } + ] + } }, { "method": "attrString", @@ -77896,7 +92967,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "prepaid-card" }, { "method": "attrString", @@ -77994,14 +93066,86 @@ "name": "aliasType", "wireName": "alias_type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_MOBILENUMBER_NUMERICID", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "mobile_number", + "value": "mobile_number" + }, + { + "name": "numeric_id", + "value": "numeric_id" + } + ] + } }, { "method": "maybeAttrEnum", "name": "aliasStatus", "wireName": "alias_status", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_ACTIVE_ACTIVEPENDING_ACTIVESTATUSPENDING_AVAILABLE_AVAILABLEPENDING_AVAILABLESTATUSPENDING_BLOCKED_DEREGISTERED_DEREGISTEREDPENDING_INACTIVE_INACTIVEPENDING_INACTIVESTATUSPENDING_UNKNOWN", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "active", + "value": "active" + }, + { + "name": "active_pending", + "value": "active_pending" + }, + { + "name": "active_status_pending", + "value": "active_status_pending" + }, + { + "name": "available", + "value": "available" + }, + { + "name": "available_pending", + "value": "available_pending" + }, + { + "name": "available_status_pending", + "value": "available_status_pending" + }, + { + "name": "blocked", + "value": "blocked" + }, + { + "name": "deregistered", + "value": "deregistered" + }, + { + "name": "deregistered_pending", + "value": "deregistered_pending" + }, + { + "name": "inactive", + "value": "inactive" + }, + { + "name": "inactive_pending", + "value": "inactive_pending" + }, + { + "name": "inactive_status_pending", + "value": "inactive_status_pending" + }, + { + "name": "unknown", + "value": "unknown" + } + ] + } } ], "repeats": true @@ -78018,14 +93162,29 @@ "name": "type", "wireName": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_PAYONDELIVERY_PIXKEY", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "pay_on_delivery", + "value": "pay_on_delivery" + }, + { + "name": "pix_key", + "value": "pix_key" + } + ] + } }, { "method": "maybeAttrString", "name": "country", "wireName": "country", "type": "string", - "required": false + "required": false, + "literalValue": "BR" }, { "method": "maybeAttrString", @@ -78039,7 +93198,21 @@ "name": "flow", "wireName": "flow", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_P2M_P2P", + "module": "WASmaxInBrPaymentEnums", + "variants": [ + { + "name": "p2m", + "value": "p2m" + }, + { + "name": "p2p", + "value": "p2p" + } + ] + } }, { "method": "attrString", @@ -78109,7 +93282,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -78122,10 +93296,24 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", "value": "error" + }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] } ], "fields": [ @@ -78134,7 +93322,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "attrString", @@ -78235,6 +93424,20 @@ "wireName": "type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_IMAGE_PREVIEW", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "preview", + "value": "preview" + } + ] + }, "sourcePath": [ "picture" ] @@ -78275,6 +93478,20 @@ "wireName": "has_staging", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "picture" ] @@ -78284,7 +93501,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -78297,6 +93515,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78320,6 +93552,20 @@ "wireName": "type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_IMAGE_PREVIEW", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "preview", + "value": "preview" + } + ] + }, "sourcePath": [ "picture" ] @@ -78360,6 +93606,20 @@ "wireName": "has_staging", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "picture" ] @@ -78369,7 +93629,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -78382,6 +93643,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78394,7 +93669,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -78438,6 +93714,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78461,6 +93751,20 @@ "wireName": "type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_IMAGE_PREVIEW", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "image", + "value": "image" + }, + { + "name": "preview", + "value": "preview" + } + ] + }, "sourcePath": [ "picture" ] @@ -78471,6 +93775,20 @@ "wireName": "has_staging", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInProfilePictureEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "picture" ] @@ -78489,7 +93807,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -78502,6 +93821,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78514,7 +93847,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -78522,11 +93856,43 @@ "tag": "GetResponseError", "moduleName": "WASmaxInProfilePictureGetResponseError", "kind": "error", + "errorCodes": [ + 400, + 401, + 404, + 429, + 500, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "internal-server-error", + "item-not-found", + "not-authorized", + "rate-overlimit", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78539,7 +93905,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -78555,14 +93922,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -78574,6 +93943,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -78585,14 +93959,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -78604,6 +93980,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -78615,14 +93996,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "item-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "404" } ], "assertions": [ @@ -78634,6 +94017,11 @@ "kind": "attr", "name": "text", "value": "item-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "404" } ] }, @@ -78645,14 +94033,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -78664,6 +94054,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -78675,14 +94070,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -78694,6 +94091,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -78705,14 +94107,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -78724,6 +94128,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] }, @@ -78735,14 +94144,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -78754,6 +94165,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] } @@ -78966,7 +94382,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -78979,6 +94396,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -78991,7 +94422,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -78999,11 +94431,35 @@ "tag": "SendBufferResponseErrorNoRetry", "moduleName": "WASmaxInStatsSendBufferResponseErrorNoRetry", "kind": "error", + "errorCodes": [ + 400, + 406, + 501 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "not-acceptable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -79016,7 +94472,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -79032,14 +94489,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -79051,6 +94510,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -79062,14 +94526,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-acceptable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "406" }, { "method": "child", @@ -79105,6 +94571,11 @@ "kind": "attr", "name": "text", "value": "not-acceptable" + }, + { + "kind": "attr", + "name": "code", + "value": "406" } ] }, @@ -79116,14 +94587,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -79135,6 +94608,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -79154,6 +94632,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -79173,14 +94665,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "repeats": false @@ -79190,7 +94684,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" } ] } @@ -79337,14 +94832,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -79398,7 +94895,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -79411,7 +94909,21 @@ "method": "attrEnum", "name": "elementValue", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -79427,6 +94939,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -79446,14 +94972,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -79507,7 +95035,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -79520,7 +95049,21 @@ "method": "attrEnum", "name": "elementValue", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -79531,11 +95074,53 @@ "tag": "EncryptedPayloadRequestResponseError", "moduleName": "WASmaxInWaffleEncryptedPayloadRequestResponseError", "kind": "error", + "errorCodes": [ + 400, + 401, + 405, + 408, + 409, + 429, + 480, + 483, + 484, + 485, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "not-authorized", + "payload-enc-dec-failed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-not-found", + "wf-state-mismatch", + "wf-suspended" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -79548,7 +95133,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -79564,14 +95150,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -79583,6 +95171,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -79594,14 +95187,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -79613,6 +95208,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -79624,14 +95224,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-enc-dec-failed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "480" } ], "assertions": [ @@ -79643,6 +95245,11 @@ "kind": "attr", "name": "text", "value": "payload-enc-dec-failed" + }, + { + "kind": "attr", + "name": "code", + "value": "480" } ] }, @@ -79654,14 +95261,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -79673,6 +95282,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -79684,14 +95298,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -79703,6 +95319,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -79714,14 +95335,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -79733,6 +95356,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -79744,14 +95372,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -79763,6 +95393,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -79774,14 +95409,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -79793,6 +95430,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -79804,14 +95446,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -79823,6 +95467,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -79834,14 +95483,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "483" } ], "assertions": [ @@ -79853,6 +95504,11 @@ "kind": "attr", "name": "text", "value": "wf-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "483" } ] }, @@ -79864,28 +95520,58 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "484" }, { "method": "maybeAttrEnum", "name": "ndc", "wireName": "ndc", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "maybeAttrEnum", "name": "npr", "wireName": "npr", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "assertions": [ @@ -79897,6 +95583,11 @@ "kind": "attr", "name": "text", "value": "wf-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "484" } ] }, @@ -79908,14 +95599,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-state-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "485" } ], "assertions": [ @@ -79927,6 +95620,11 @@ "kind": "attr", "name": "text", "value": "wf-state-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "485" } ] } @@ -79979,7 +95677,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -79992,6 +95691,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80004,7 +95717,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -80012,11 +95726,41 @@ "tag": "ForceDeleteStateResponseError", "moduleName": "WASmaxInWaffleForceDeleteStateResponseError", "kind": "error", + "errorCodes": [ + 400, + 405, + 408, + 429, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "internal-server-error", + "not-allowed", + "rate-overlimit", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80029,7 +95773,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -80045,14 +95790,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -80064,6 +95811,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -80075,14 +95827,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -80094,6 +95848,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -80105,14 +95864,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -80124,6 +95885,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -80135,14 +95901,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -80154,6 +95922,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -80165,14 +95938,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -80184,6 +95959,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -80195,14 +95975,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -80214,6 +95996,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] } @@ -80259,6 +96046,20 @@ "name": "nprElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "npr" ] @@ -80268,7 +96069,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -80281,6 +96083,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80293,6 +96109,20 @@ "name": "nprElementValue", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "npr" ] @@ -80302,7 +96132,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -80310,11 +96141,47 @@ "tag": "ForceSuspendStateResponseError", "moduleName": "WASmaxInWaffleForceSuspendStateResponseError", "kind": "error", + "errorCodes": [ + 400, + 405, + 408, + 409, + 429, + 483, + 485, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-not-found", + "wf-state-mismatch" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80327,7 +96194,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -80343,14 +96211,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -80362,6 +96232,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -80373,14 +96248,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -80392,6 +96269,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -80403,14 +96285,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -80422,6 +96306,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -80433,14 +96322,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -80452,6 +96343,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -80463,14 +96359,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -80482,6 +96380,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -80493,14 +96396,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -80512,6 +96417,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -80523,14 +96433,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -80542,6 +96454,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -80553,14 +96470,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "483" } ], "assertions": [ @@ -80572,6 +96491,11 @@ "kind": "attr", "name": "text", "value": "wf-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "483" } ] }, @@ -80583,14 +96507,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-state-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "485" } ], "assertions": [ @@ -80602,6 +96528,11 @@ "kind": "attr", "name": "text", "value": "wf-state-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "485" } ] } @@ -80681,14 +96612,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -80742,7 +96675,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -80755,6 +96689,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80783,14 +96731,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -80844,7 +96794,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -80852,11 +96803,55 @@ "tag": "GenerateAccessTokensResponseError", "moduleName": "WASmaxInWaffleGenerateAccessTokensResponseError", "kind": "error", + "errorCodes": [ + 304, + 400, + 401, + 405, + 408, + 409, + 416, + 429, + 480, + 483, + 485, + 500, + 503 + ], + "errorTexts": [ + "already-exists", + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "not-authorized", + "payload-enc-dec-failed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-not-authorized-invalid-password", + "wf-not-found", + "wf-state-mismatch" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -80869,7 +96864,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -80885,14 +96881,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -80904,6 +96902,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -80915,14 +96918,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -80934,6 +96939,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -80945,14 +96955,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-enc-dec-failed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "480" } ], "assertions": [ @@ -80964,6 +96976,11 @@ "kind": "attr", "name": "text", "value": "payload-enc-dec-failed" + }, + { + "kind": "attr", + "name": "code", + "value": "480" } ] }, @@ -80975,14 +96992,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -80994,6 +97013,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -81005,14 +97029,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -81024,6 +97050,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -81035,14 +97066,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -81054,6 +97087,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -81065,14 +97103,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -81084,6 +97124,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -81095,14 +97140,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -81114,6 +97161,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -81125,14 +97177,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -81144,6 +97198,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -81155,14 +97214,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "already-exists" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -81174,6 +97235,11 @@ "kind": "attr", "name": "text", "value": "already-exists" + }, + { + "kind": "attr", + "name": "code", + "value": "304" } ] }, @@ -81185,14 +97251,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "483" } ], "assertions": [ @@ -81204,6 +97272,11 @@ "kind": "attr", "name": "text", "value": "wf-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "483" } ] }, @@ -81215,14 +97288,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-state-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "485" } ], "assertions": [ @@ -81234,6 +97309,11 @@ "kind": "attr", "name": "text", "value": "wf-state-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "485" } ] }, @@ -81245,14 +97325,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-authorized-invalid-password" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -81264,6 +97346,11 @@ "kind": "attr", "name": "text", "value": "wf-not-authorized-invalid-password" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] } @@ -81349,14 +97436,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -81410,7 +97499,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -81423,6 +97513,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -81442,14 +97546,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -81503,7 +97609,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -81511,11 +97618,51 @@ "tag": "GenerateWAEntACUserResponseError", "moduleName": "WASmaxInWaffleGenerateWAEntACUserResponseError", "kind": "error", + "errorCodes": [ + 304, + 400, + 401, + 405, + 408, + 409, + 418, + 429, + 480, + 500, + 503 + ], + "errorTexts": [ + "already-exists", + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "not-authorized", + "payload-enc-dec-failed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-jurisdiction-gating" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -81528,7 +97675,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -81544,14 +97692,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -81563,6 +97713,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -81574,14 +97729,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -81593,6 +97750,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -81604,14 +97766,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-enc-dec-failed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "480" } ], "assertions": [ @@ -81623,6 +97787,11 @@ "kind": "attr", "name": "text", "value": "payload-enc-dec-failed" + }, + { + "kind": "attr", + "name": "code", + "value": "480" } ] }, @@ -81634,14 +97803,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -81653,6 +97824,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -81664,14 +97840,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -81683,6 +97861,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -81694,14 +97877,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -81713,6 +97898,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -81724,14 +97914,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -81743,6 +97935,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -81754,14 +97951,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -81773,6 +97972,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -81784,14 +97988,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -81803,6 +98009,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -81814,14 +98025,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "already-exists" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "304" } ], "assertions": [ @@ -81833,6 +98046,11 @@ "kind": "attr", "name": "text", "value": "already-exists" + }, + { + "kind": "attr", + "name": "code", + "value": "304" } ] }, @@ -81844,14 +98062,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-jurisdiction-gating" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "418" } ], "assertions": [ @@ -81863,6 +98083,11 @@ "kind": "attr", "name": "text", "value": "wf-jurisdiction-gating" + }, + { + "kind": "attr", + "name": "code", + "value": "418" } ] } @@ -82021,7 +98246,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -82034,6 +98260,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -82145,7 +98385,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -82153,11 +98394,43 @@ "tag": "GetCertificateResponseError", "moduleName": "WASmaxInWaffleGetCertificateResponseError", "kind": "error", + "errorCodes": [ + 400, + 405, + 408, + 429, + 500, + 501, + 503 + ], + "errorTexts": [ + "bad-request", + "feature-not-implemented", + "internal-server-error", + "not-allowed", + "rate-overlimit", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -82170,7 +98443,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -82186,14 +98460,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -82205,6 +98481,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -82216,14 +98497,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -82235,6 +98518,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -82246,14 +98534,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -82265,6 +98555,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -82276,14 +98571,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -82295,6 +98592,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -82306,14 +98608,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -82325,6 +98629,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -82336,14 +98645,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -82355,6 +98666,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -82366,14 +98682,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "feature-not-implemented" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "501" } ], "assertions": [ @@ -82385,6 +98703,11 @@ "kind": "attr", "name": "text", "value": "feature-not-implemented" + }, + { + "kind": "attr", + "name": "code", + "value": "501" } ] } @@ -82449,14 +98772,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -82510,7 +98835,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -82523,6 +98849,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -82542,14 +98882,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "rsa2048" }, { "method": "contentBytes", @@ -82603,7 +98945,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -82611,11 +98954,55 @@ "tag": "RefreshAccessTokensResponseError", "moduleName": "WASmaxInWaffleRefreshAccessTokensResponseError", "kind": "error", + "errorCodes": [ + 400, + 401, + 405, + 408, + 409, + 416, + 429, + 480, + 483, + 484, + 485, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "not-authorized", + "payload-enc-dec-failed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-not-authorized-invalid-password", + "wf-not-found", + "wf-state-mismatch", + "wf-suspended" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -82628,7 +99015,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -82644,14 +99032,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -82663,6 +99053,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -82674,14 +99069,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -82693,6 +99090,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -82704,14 +99106,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-enc-dec-failed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "480" } ], "assertions": [ @@ -82723,6 +99127,11 @@ "kind": "attr", "name": "text", "value": "payload-enc-dec-failed" + }, + { + "kind": "attr", + "name": "code", + "value": "480" } ] }, @@ -82734,14 +99143,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -82753,6 +99164,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -82764,14 +99180,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -82783,6 +99201,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -82794,14 +99217,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -82813,6 +99238,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -82824,14 +99254,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -82843,6 +99275,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -82854,14 +99291,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -82873,6 +99312,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -82884,14 +99328,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -82903,6 +99349,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -82914,14 +99365,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "483" } ], "assertions": [ @@ -82933,6 +99386,11 @@ "kind": "attr", "name": "text", "value": "wf-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "483" } ] }, @@ -82944,28 +99402,58 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "484" }, { "method": "maybeAttrEnum", "name": "ndc", "wireName": "ndc", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "maybeAttrEnum", "name": "npr", "wireName": "npr", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "assertions": [ @@ -82977,6 +99465,11 @@ "kind": "attr", "name": "text", "value": "wf-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "484" } ] }, @@ -82988,14 +99481,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-state-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "485" } ], "assertions": [ @@ -83007,6 +99502,11 @@ "kind": "attr", "name": "text", "value": "wf-state-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "485" } ] }, @@ -83018,14 +99518,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-authorized-invalid-password" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "416" } ], "assertions": [ @@ -83037,6 +99539,11 @@ "kind": "attr", "name": "text", "value": "wf-not-authorized-invalid-password" + }, + { + "kind": "attr", + "name": "code", + "value": "416" } ] } @@ -83091,7 +99598,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -83105,7 +99613,21 @@ "name": "npr", "wireName": "npr", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -83121,6 +99643,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -83142,7 +99678,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" }, { "method": "child", @@ -83156,7 +99693,21 @@ "name": "npr", "wireName": "npr", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "repeats": false @@ -83167,11 +99718,41 @@ "tag": "StateExistsResponseError", "moduleName": "WASmaxInWaffleStateExistsResponseError", "kind": "error", + "errorCodes": [ + 400, + 405, + 408, + 429, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "internal-server-error", + "not-allowed", + "rate-overlimit", + "request-timeout", + "service-unavailable" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -83184,7 +99765,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -83200,14 +99782,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -83219,6 +99803,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -83230,14 +99819,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -83249,6 +99840,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -83260,14 +99856,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -83279,6 +99877,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -83290,14 +99893,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -83309,6 +99914,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -83320,14 +99930,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -83339,6 +99951,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -83350,14 +99967,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -83369,6 +99988,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] } @@ -83435,7 +100059,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ], "variants": [ @@ -83448,6 +100073,20 @@ "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -83469,7 +100108,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "result" } ] }, @@ -83477,11 +100117,53 @@ "tag": "WFPingResponseError", "moduleName": "WASmaxInWaffleWFPingResponseError", "kind": "error", + "errorCodes": [ + 400, + 401, + 405, + 408, + 409, + 429, + 480, + 483, + 484, + 485, + 500, + 503 + ], + "errorTexts": [ + "bad-request", + "conflict", + "internal-server-error", + "not-allowed", + "not-authorized", + "payload-enc-dec-failed", + "rate-overlimit", + "request-timeout", + "service-unavailable", + "wf-not-found", + "wf-state-mismatch", + "wf-suspended" + ], "assertions": [ { "kind": "tag", "name": "iq" }, + { + "kind": "reference", + "name": "id", + "referencePath": [ + "id" + ] + }, + { + "kind": "reference", + "name": "from", + "referencePath": [ + "to" + ] + }, { "kind": "attr", "name": "type", @@ -83494,7 +100176,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "error" }, { "method": "", @@ -83510,14 +100193,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "bad-request" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "400" } ], "assertions": [ @@ -83529,6 +100214,11 @@ "kind": "attr", "name": "text", "value": "bad-request" + }, + { + "kind": "attr", + "name": "code", + "value": "400" } ] }, @@ -83540,14 +100230,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "request-timeout" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "408" } ], "assertions": [ @@ -83559,6 +100251,11 @@ "kind": "attr", "name": "text", "value": "request-timeout" + }, + { + "kind": "attr", + "name": "code", + "value": "408" } ] }, @@ -83570,14 +100267,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "payload-enc-dec-failed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "480" } ], "assertions": [ @@ -83589,6 +100288,11 @@ "kind": "attr", "name": "text", "value": "payload-enc-dec-failed" + }, + { + "kind": "attr", + "name": "code", + "value": "480" } ] }, @@ -83600,14 +100304,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "internal-server-error" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "500" } ], "assertions": [ @@ -83619,6 +100325,11 @@ "kind": "attr", "name": "text", "value": "internal-server-error" + }, + { + "kind": "attr", + "name": "code", + "value": "500" } ] }, @@ -83630,14 +100341,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "service-unavailable" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "503" } ], "assertions": [ @@ -83649,6 +100362,11 @@ "kind": "attr", "name": "text", "value": "service-unavailable" + }, + { + "kind": "attr", + "name": "code", + "value": "503" } ] }, @@ -83660,14 +100378,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-authorized" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "401" } ], "assertions": [ @@ -83679,6 +100399,11 @@ "kind": "attr", "name": "text", "value": "not-authorized" + }, + { + "kind": "attr", + "name": "code", + "value": "401" } ] }, @@ -83690,14 +100415,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "rate-overlimit" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "429" } ], "assertions": [ @@ -83709,6 +100436,11 @@ "kind": "attr", "name": "text", "value": "rate-overlimit" + }, + { + "kind": "attr", + "name": "code", + "value": "429" } ] }, @@ -83720,14 +100452,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "not-allowed" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "405" } ], "assertions": [ @@ -83739,6 +100473,11 @@ "kind": "attr", "name": "text", "value": "not-allowed" + }, + { + "kind": "attr", + "name": "code", + "value": "405" } ] }, @@ -83750,14 +100489,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "conflict" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "409" } ], "assertions": [ @@ -83769,6 +100510,11 @@ "kind": "attr", "name": "text", "value": "conflict" + }, + { + "kind": "attr", + "name": "code", + "value": "409" } ] }, @@ -83780,14 +100526,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-not-found" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "483" } ], "assertions": [ @@ -83799,6 +100547,11 @@ "kind": "attr", "name": "text", "value": "wf-not-found" + }, + { + "kind": "attr", + "name": "code", + "value": "483" } ] }, @@ -83810,28 +100563,58 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-suspended" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "484" }, { "method": "maybeAttrEnum", "name": "ndc", "wireName": "ndc", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } }, { "method": "maybeAttrEnum", "name": "npr", "wireName": "npr", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + } } ], "assertions": [ @@ -83843,6 +100626,11 @@ "kind": "attr", "name": "text", "value": "wf-suspended" + }, + { + "kind": "attr", + "name": "code", + "value": "484" } ] }, @@ -83854,14 +100642,16 @@ "name": "text", "wireName": "text", "type": "string", - "required": true + "required": true, + "literalValue": "wf-state-mismatch" }, { "method": "attrInt", "name": "code", "wireName": "code", "type": "integer", - "required": true + "required": true, + "literalValue": "485" } ], "assertions": [ @@ -83873,6 +100663,11 @@ "kind": "attr", "name": "text", "value": "wf-state-mismatch" + }, + { + "kind": "attr", + "name": "code", + "value": "485" } ] } diff --git a/generated/manifest.json b/generated/manifest.json index 7b3e2ec..bb615e3 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -4,6 +4,13 @@ "diagnostics": { "iq": { "candidateModules": 207, + "constraints": { + "errorTexts": 35, + "fieldEnumRefs": 277, + "fieldLiterals": 2174, + "referenceConstraints": 579, + "typedErrorVariants": 75 + }, "crossModule": { "fieldsRecovered": 88, "requestsEnriched": 11, @@ -12,7 +19,8 @@ "degradedResponses": 4, "dropsByReason": { "iq builder substring present but no AST iq call": 5, - "mixin fragment (folded into requests, not a standalone stanza)": 66 + "mixin fragment (folded into requests, not a standalone stanza)": 66, + "response enum argument not structurally resolvable": 10 }, "excludedFragments": 66, "stanzas": 143, @@ -20,6 +28,7 @@ "unparseable": 5 }, "notif": { + "actions": 55, "degraded": 5, "stanzaTags": 13, "typedContent": 22, @@ -30,37 +39,37 @@ "abprops": { "file": "abprops/index.json", "schema": "schema/abprops.schema.json", - "sha256": "efa3cb733c8533fc1910f7f93fa52288e3368b1b55599fe96b647fa75eedc885" + "sha256": "63cda1b8581c73c73b74c624a273d7d8079727142023f213dc9d59dcc027c6fa" }, "appstate": { "file": "appstate/index.json", "schema": "schema/appstate.schema.json", - "sha256": "ed9e161621be8eaaa75787a1365a06dfe435ceea5218fdcafc18ce26082f8593" + "sha256": "861c98b83183e8a430adaafd015c043010922afabb497a4b6d3f81bba270b9a5" }, "enums": { "file": "enums/index.json", "schema": "schema/enums.schema.json", - "sha256": "af2c1bc1f6afa9d33c0b8ccbfb14142b00c0c0f35ec4a7ba93a9a577b9b9277d" + "sha256": "3c3f6eec50a86cabb3afb2ed6da234a3e289bd6849d0c2046e5f1e6f5260c3f8" }, "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "3c1aa63a212cb3e7d08842b62bf10b88aa9bfa7fb3ca7ee3a9fa62b94a1d4957" + "sha256": "3c8baa4affd8f9f7a9ad7f96a43fad2312708a6004bee9f2383c665990096c42" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "4792a9a6e438a47948f536bd43364ceb0ffc64b553d7ff76b399691053ba4013" + "sha256": "27cdff784d39eb49190bb7b2fea7160c8190892c9c978a50621b7c06ef832800" }, "mex": { "file": "mex/index.json", "schema": "schema/mex.schema.json", - "sha256": "00b50b4edd26b1f3b64b47a83da460cd571cabae2bd7f9e8dd225db1fc42ba66" + "sha256": "5865b9e19925e6fc78ba93fc63b2f919d717dc028d216a3964993fc861d53d31" }, "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "08eab8e81dcc2c8ac0739ef0f050c5b6ca6860dca786f1af1a4dfdd8526b64d2" + "sha256": "a828c2f7ba52343e2702386b47bc07b7a93a5fa70eb213e6ea86006e4ea9a31a" }, "proto": { "file": "proto/WAProto.proto", @@ -69,27 +78,27 @@ "srvreq": { "file": "srvreq/index.json", "schema": "schema/srvreq.schema.json", - "sha256": "4529564d4e68070c2a6dd9d872550b1233d3ab7a80d1a346b3a0374d8413be63" + "sha256": "bd2c606ce005b84eabbc413c9211ce381b32cd6e066112dedd82058a4ef847a5" }, "stanza": { "file": "stanza/index.json", "schema": "schema/stanza.schema.json", - "sha256": "ab2e7bc4584b524aafdd70fd9c0aa18701347b722b77ae3262c74c5debdd0298" + "sha256": "13a78fcf9b5e09b7cc9af26657aa95a9ba1c7035be6513238b913166ea7fcd08" }, "tokens": { "file": "tokens/index.json", "schema": "schema/tokens.schema.json", - "sha256": "d0ab67e3f17f152f65785c617e9e854dc1488ba019423b4b44854681d1ac76fd" + "sha256": "7c1b8c84940449264b3a4966b7a61d0602ad405c85bfb6a5be38bda6b531080c" }, "wam": { "file": "wam/index.json", "schema": "schema/wam.schema.json", - "sha256": "aa673facefa5a0c6e61a946f3e83e4daaa3c5a0c405bda25acc15daf0792a531" + "sha256": "a89414f0259598f7544302dbb6bad805c32fd62e63f67f7657ae581a99a9ebf3" }, "wasm": { "file": "wasm/index.json", "schema": "schema/wasm.schema.json", - "sha256": "da85bc11e4d5a7f47d1c65250780c02565f256888dac047fd817f5f9e4174f7a" + "sha256": "fd6bab59135b2bd147074bf2bb802d7db4ea289541629758e141a91954a31d4e" } }, "enumDefs": 326, @@ -99,7 +108,7 @@ "mexOperations": 134, "notifTypes": 27, "protoEntities": 264, - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "serverRequestDefs": 22, "stanzaDefs": 176, "tokenDoubleByte": 1024, diff --git a/generated/mex/index.json b/generated/mex/index.json index d502e47..ad45e5f 100644 --- a/generated/mex/index.json +++ b/generated/mex/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "operations": { "ACSServerProviderConfig": { diff --git a/generated/notif/index.json b/generated/notif/index.json index bbe34e3..00cc2db 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "dispatcherModules": [ "WAWebCommsHandleLoggedInStanza", @@ -359,7 +359,42 @@ "required": true } ] - } + }, + "actions": [ + { + "wireTag": "status" + }, + { + "wireTag": "text_status" + }, + { + "wireTag": "devices" + }, + { + "wireTag": "picture" + }, + { + "wireTag": "privacy" + }, + { + "wireTag": "blocklist" + }, + { + "wireTag": "biz_opt_out_list" + }, + { + "wireTag": "tos" + }, + { + "wireTag": "disappearing_mode" + }, + { + "wireTag": "notice" + }, + { + "wireTag": "user" + } + ] }, { "type": "business", @@ -641,7 +676,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "crsc_continuation" }, { "method": "attrJidWithType", @@ -1062,7 +1098,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "passkey_prologue_request" }, { "method": "attrJidWithType", @@ -1738,7 +1775,987 @@ "required": false } ] - } + }, + "actions": [ + { + "wireTag": "create", + "actionType": "create", + "fields": [ + { + "name": "reason", + "wireName": "reason", + "type": "string", + "required": false + }, + { + "name": "contextGroupId", + "wireName": "context_group_jid", + "type": "group_jid", + "required": false + } + ] + }, + { + "wireTag": "add", + "actionType": "add", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "delete", + "actionType": "delete" + }, + { + "wireTag": "remove", + "actionType": "remove", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "promote", + "actionType": "promote", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "demote", + "actionType": "demote", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "linked_group_promote", + "actionType": "linked_group_promote", + "fields": [ + { + "name": "jid", + "wireName": "jid", + "type": "group_jid", + "required": false + } + ], + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "linked_group_demote", + "actionType": "linked_group_demote", + "fields": [ + { + "name": "jid", + "wireName": "jid", + "type": "group_jid", + "required": false + } + ], + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "modify", + "actionType": "modify", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "displayName", + "wireName": "display_name", + "type": "string", + "required": false + }, + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "lid", + "wireName": "lid", + "type": "lid_user_jid", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "string", + "required": false + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "joinTime", + "wireName": "join_time", + "type": "integer", + "required": false + }, + { + "name": "groupHistorySentState", + "wireName": "group_history_sent_state", + "type": "string", + "required": false + } + ] + } + ] + }, + { + "wireTag": "subject", + "actionType": "subject", + "fields": [ + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "s_o", + "wireName": "s_o", + "type": "user_jid", + "required": false + }, + { + "name": "subjectOwnerPn", + "wireName": "s_o_pn", + "type": "user_jid", + "required": false + }, + { + "name": "subjectOwnerUsername", + "wireName": "s_o_username", + "type": "string", + "required": false + }, + { + "name": "s_t", + "wireName": "s_t", + "type": "integer", + "required": false + } + ] + }, + { + "wireTag": "description", + "actionType": "desc_remove", + "fields": [ + { + "name": "descId", + "wireName": "id", + "type": "string", + "required": true + } + ] + }, + { + "wireTag": "description", + "actionType": "desc_add", + "fields": [ + { + "name": "descId", + "wireName": "id", + "type": "string", + "required": true + }, + { + "name": "desc", + "wireName": "body", + "type": "string", + "required": false, + "content": true + } + ] + }, + { + "wireTag": "suspended", + "actionType": "suspend", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "unsuspended", + "actionType": "suspend", + "constantFields": [ + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "locked", + "actionType": "restrict", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "unlocked", + "actionType": "restrict", + "constantFields": [ + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "announcement", + "actionType": "announce", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "not_announcement", + "actionType": "announce", + "constantFields": [ + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "no_frequently_forwarded", + "actionType": "no_frequently_forwarded", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "frequently_forwarded_ok", + "actionType": "no_frequently_forwarded", + "constantFields": [ + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "invite", + "actionType": "invite", + "fields": [ + { + "name": "code", + "wireName": "code", + "type": "string", + "required": true + } + ] + }, + { + "wireTag": "ephemeral", + "actionType": "ephemeral", + "fields": [ + { + "name": "duration", + "wireName": "expiration", + "type": "integer", + "required": true + }, + { + "name": "trigger", + "wireName": "trigger", + "type": "integer", + "required": false + } + ] + }, + { + "wireTag": "not_ephemeral", + "actionType": "ephemeral", + "constantFields": [ + { + "name": "duration", + "value": 0 + } + ] + }, + { + "wireTag": "revoke", + "actionType": "revoke_invite", + "children": [ + { + "name": "participants", + "wireTag": "participant", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "expiration", + "wireName": "expiration", + "type": "integer", + "required": true + } + ] + } + ] + }, + { + "wireTag": "growth_unlocked", + "actionType": "growth_unlocked" + }, + { + "wireTag": "growth_locked", + "actionType": "growth_locked", + "fields": [ + { + "name": "expiration", + "wireName": "expiration", + "type": "integer", + "required": true + }, + { + "name": "type", + "wireName": "type", + "type": "string", + "required": true + } + ] + }, + { + "wireTag": "link", + "children": [ + { + "name": "groupDatas", + "wireTag": "group", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "subjectTime", + "wireName": "s_t", + "type": "integer", + "required": true + } + ] + } + ] + }, + { + "wireTag": "unlink", + "children": [ + { + "name": "groupDatas", + "wireTag": "group", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "subjectTime", + "wireName": "s_t", + "type": "integer", + "required": true + } + ] + } + ] + }, + { + "wireTag": "membership_approval_mode", + "actionType": "membership_approval_mode", + "fields": [ + { + "name": "triggered", + "wireName": "triggered", + "type": "string", + "required": false + } + ] + }, + { + "wireTag": "membership_approval_request", + "actionType": "membership_approval_request", + "fields": [ + { + "name": "parentGroupId", + "wireName": "parent_group_jid", + "type": "group_jid", + "required": false + } + ] + }, + { + "wireTag": "allow_admin_reports", + "actionType": "allow_admin_reports", + "fields": [ + { + "name": "triggered", + "wireName": "triggered", + "type": "string", + "required": false + } + ], + "constantFields": [ + { + "name": "shouldSkipGenMsg", + "value": false + }, + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "not_allow_admin_reports", + "actionType": "allow_admin_reports", + "fields": [ + { + "name": "triggered", + "wireName": "triggered", + "type": "string", + "required": false + } + ], + "constantFields": [ + { + "name": "shouldSkipGenMsg", + "value": false + }, + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "reports", + "actionType": "admin_report_received", + "constantFields": [ + { + "name": "shouldSkipGenMsg", + "value": true + } + ] + }, + { + "wireTag": "created_membership_requests", + "actionType": "created_membership_requests", + "fields": [ + { + "name": "parentGroupId", + "wireName": "parent_group_jid", + "type": "group_jid", + "required": false + } + ], + "children": [ + { + "name": "requests", + "wireTag": "requested_user", + "fields": [ + { + "name": "wid", + "wireName": "jid", + "type": "user_jid", + "required": true + }, + { + "name": "username", + "wireName": "username", + "type": "string", + "required": false + }, + { + "name": "phoneNumber", + "wireName": "phone_number", + "type": "user_jid", + "required": false + } + ] + } + ] + }, + { + "wireTag": "revoked_membership_requests", + "actionType": "revoked_membership_requests", + "children": [ + { + "name": "requests", + "wireTag": "participant", + "fields": [ + { + "name": "jid", + "wireName": "jid", + "type": "user_jid", + "required": true + } + ] + } + ] + }, + { + "wireTag": "allow_non_admin_sub_group_creation", + "actionType": "allow_non_admin_sub_group_creation", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "not_allow_non_admin_sub_group_creation", + "actionType": "allow_non_admin_sub_group_creation", + "constantFields": [ + { + "name": "value", + "value": false + } + ] + }, + { + "wireTag": "created_sub_group_suggestion", + "actionType": "created_subgroup_suggestion", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "owner", + "wireName": "creator", + "type": "user_jid", + "required": true + }, + { + "name": "subject", + "wireName": "", + "type": "string", + "required": true, + "content": true + }, + { + "name": "t", + "wireName": "creation", + "type": "integer", + "required": true + } + ] + }, + { + "wireTag": "revoked_sub_group_suggestions", + "actionType": "revoked_sub_group_suggestions", + "children": [ + { + "name": "subgroupSuggestions", + "wireTag": "sub_group_suggestion", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "owner", + "wireName": "creator", + "type": "user_jid", + "required": true + } + ] + } + ] + }, + { + "wireTag": "change_number", + "actionType": "subgroup_suggestions_change_number", + "fields": [ + { + "name": "oldOwner", + "wireName": "participant", + "type": "user_jid", + "required": true + }, + { + "name": "newOwner", + "wireName": "jid", + "type": "user_jid", + "required": true + } + ], + "children": [ + { + "name": "subgroupSuggestions", + "wireTag": "sub_group_suggestion", + "fields": [ + { + "name": "jid", + "wireName": "jid", + "type": "group_jid", + "required": true + } + ] + } + ] + }, + { + "wireTag": "member_add_mode", + "actionType": "member_add_mode", + "fields": [ + { + "name": "memberAddMode", + "wireName": "", + "type": "string", + "required": true, + "content": true + } + ] + }, + { + "wireTag": "auto_add_disabled", + "actionType": "general_chat_auto_add_disabled" + }, + { + "wireTag": "group_safety_check", + "actionType": "group_safety_check", + "constantFields": [ + { + "name": "value", + "value": true + } + ] + }, + { + "wireTag": "missing_participant_identification" + } + ] }, { "type": "w:growth", @@ -1832,7 +2849,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "waffle" }, { "method": "attrInt", @@ -1852,6 +2870,20 @@ "wireName": "show_user_notif", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] @@ -1874,6 +2906,20 @@ "wireName": "client_resync", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] @@ -1894,6 +2940,20 @@ "wireName": "npr", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index 5f780a1..8399ed1 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -40,7 +40,54 @@ "description": "The node's text content is pinned to a fixed value (`literalContent(content,\nnode, \"admin_add\")`) — a discriminator for marker union variants. The value is\nin [`ResponseAssertion::value`]; `name` is unused.", "type": "string", "const": "content" + }, + { + "description": "The attribute named by [`ResponseAssertion::name`] must **echo a value taken\nfrom the request** — `literal(attrString, node, \"from\",\nattrStringFromReference(request, [\"to\"]))`. The expected value is not a\nconstant, so [`ResponseAssertion::value`] is absent; where it comes from is in\n[`ResponseAssertion::reference_path`].\n\nThis is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success\nparser) enforce: an answer's `id` must equal the request's `id` and its `from`\nthe request's `to`. An emitter that hardcodes `from=\"s.whatsapp.net\"` produces\nstanzas a real client cannot parse whenever the request went to `g.us`.", + "type": "string", + "const": "reference" + } + ] + }, + "AttrEnumRef": { + "description": "A wire enum a request attribute's value is drawn from, recovered by resolving a\nstructural `o(\"Mod\").EnumName.VARIANT` reference in the builder. String-valued (all\nstanza-attr enums are wire tokens), so a consumer can generate a real enum type.", + "type": "object", + "properties": { + "module": { + "description": "The module that defines it (`WAWebBackendJobs.flow`, `WAWebUsync`, …).", + "type": "string" + }, + "name": { + "description": "The enum's name (`CiphertextType`, `USYNC_ADDRESSING_MODE`, …).", + "type": "string" + }, + "variants": { + "description": "The enum's variants in source order.", + "type": "array", + "items": { + "$ref": "#/$defs/AttrEnumVariant" + } } + }, + "required": [ + "name", + "module", + "variants" + ] + }, + "AttrEnumVariant": { + "description": "One `NAME: \"wire_value\"` member of an [`AttrEnumRef`].", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" ] }, "ContentType": { @@ -52,6 +99,21 @@ "nodes" ] }, + "ErrorClass": { + "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", + "oneOf": [ + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server" + } + ] + }, "IncomingDef": { "description": "One received stanza's read-shape: which tag it parses, the module that defines the\nparser, and the recovered field tree.", "type": "object", @@ -143,6 +205,17 @@ "type": "string" } }, + "enumRef": { + "description": "The wire enum this field's value is drawn from, when the parser reads it with an\nenum accessor (`attrStringEnum(node, \"state\", o(\"Mod\").ENUM_OFF_ON)`). Resolved\nthe same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer\ncan type the field as that enum instead of a bare string — and an emitter knows\nwhich values are legal instead of guessing from the generated `ENUM_OFF_ON` name.\nAbsent when the enum argument is not a structurally resolvable module export, or\nresolves to a non-string enum (never guessed).\n\n[`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "intMax": { "description": "Inclusive upper bound from the same `attrIntRange` check (see [`int_min`]).\n\n[`int_min`]: ParsedField::int_min", "type": [ @@ -159,6 +232,13 @@ ], "format": "int64" }, + "literalValue": { + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "type": [ + "string", + "null" + ] + }, "method": { "type": "string" }, @@ -166,6 +246,16 @@ "description": "The output (struct field) name — the camelCase `makeResult` key in smax.", "type": "string" }, + "referencePath": { + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "repeats": { "type": [ "boolean", @@ -357,6 +447,16 @@ "null" ] }, + "referencePath": { + "description": "Where the expected value is read from **in the request**, for\n[`AssertionKind::Reference`]. It is the argument list of WA's\n`attrStringFromReference(request, path)` helper: every element but the last is a\nchild tag to descend into, and the last is the attribute name. So `[\"to\"]` means\n\"the request's `to` attribute\" and `[\"account\", \"action\"]` means \"the `action`\nattribute of the request's `` child\".\n\nCarried structurally (rather than left to be inferred from `name`) so a consumer\nnever has to pattern-match attribute names to know an echo rule applies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "value": { "type": [ "string", @@ -378,6 +478,48 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorClass": { + "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorClass" + }, + { + "type": "null" + } + ] + }, + "errorCodeMax": { + "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodeMin": { + "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodes": { + "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "errorTexts": { + "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "type": "array", + "items": { + "type": "string" + } + }, "fields": { "type": "array", "items": { diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index 4342537..70b43dd 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -45,6 +45,11 @@ "description": "The node's text content is pinned to a fixed value (`literalContent(content,\nnode, \"admin_add\")`) — a discriminator for marker union variants. The value is\nin [`ResponseAssertion::value`]; `name` is unused.", "type": "string", "const": "content" + }, + { + "description": "The attribute named by [`ResponseAssertion::name`] must **echo a value taken\nfrom the request** — `literal(attrString, node, \"from\",\nattrStringFromReference(request, [\"to\"]))`. The expected value is not a\nconstant, so [`ResponseAssertion::value`] is absent; where it comes from is in\n[`ResponseAssertion::reference_path`].\n\nThis is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success\nparser) enforce: an answer's `id` must equal the request's `id` and its `from`\nthe request's `to`. An emitter that hardcodes `from=\"s.whatsapp.net\"` produces\nstanzas a real client cannot parse whenever the request went to `g.us`.", + "type": "string", + "const": "reference" } ] }, @@ -99,6 +104,21 @@ "nodes" ] }, + "ErrorClass": { + "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", + "oneOf": [ + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server" + } + ] + }, "IqRequestDef": { "description": "The outgoing request half of an IQ operation.", "type": "object", @@ -250,6 +270,17 @@ "type": "string" } }, + "enumRef": { + "description": "The wire enum this field's value is drawn from, when the parser reads it with an\nenum accessor (`attrStringEnum(node, \"state\", o(\"Mod\").ENUM_OFF_ON)`). Resolved\nthe same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer\ncan type the field as that enum instead of a bare string — and an emitter knows\nwhich values are legal instead of guessing from the generated `ENUM_OFF_ON` name.\nAbsent when the enum argument is not a structurally resolvable module export, or\nresolves to a non-string enum (never guessed).\n\n[`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "intMax": { "description": "Inclusive upper bound from the same `attrIntRange` check (see [`int_min`]).\n\n[`int_min`]: ParsedField::int_min", "type": [ @@ -266,6 +297,13 @@ ], "format": "int64" }, + "literalValue": { + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "type": [ + "string", + "null" + ] + }, "method": { "type": "string" }, @@ -273,6 +311,16 @@ "description": "The output (struct field) name — the camelCase `makeResult` key in smax.", "type": "string" }, + "referencePath": { + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "repeats": { "type": [ "boolean", @@ -464,6 +512,16 @@ "null" ] }, + "referencePath": { + "description": "Where the expected value is read from **in the request**, for\n[`AssertionKind::Reference`]. It is the argument list of WA's\n`attrStringFromReference(request, path)` helper: every element but the last is a\nchild tag to descend into, and the last is the attribute name. So `[\"to\"]` means\n\"the request's `to` attribute\" and `[\"account\", \"action\"]` means \"the `action`\nattribute of the request's `` child\".\n\nCarried structurally (rather than left to be inferred from `name`) so a consumer\nnever has to pattern-match attribute names to know an echo rule applies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "value": { "type": [ "string", @@ -485,6 +543,48 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorClass": { + "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorClass" + }, + { + "type": "null" + } + ] + }, + "errorCodeMax": { + "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodeMin": { + "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodes": { + "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "errorTexts": { + "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "type": "array", + "items": { + "type": "string" + } + }, "fields": { "type": "array", "items": { diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index db3f109..8e00082 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -56,7 +56,54 @@ "description": "The node's text content is pinned to a fixed value (`literalContent(content,\nnode, \"admin_add\")`) — a discriminator for marker union variants. The value is\nin [`ResponseAssertion::value`]; `name` is unused.", "type": "string", "const": "content" + }, + { + "description": "The attribute named by [`ResponseAssertion::name`] must **echo a value taken\nfrom the request** — `literal(attrString, node, \"from\",\nattrStringFromReference(request, [\"to\"]))`. The expected value is not a\nconstant, so [`ResponseAssertion::value`] is absent; where it comes from is in\n[`ResponseAssertion::reference_path`].\n\nThis is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success\nparser) enforce: an answer's `id` must equal the request's `id` and its `from`\nthe request's `to`. An emitter that hardcodes `from=\"s.whatsapp.net\"` produces\nstanzas a real client cannot parse whenever the request went to `g.us`.", + "type": "string", + "const": "reference" + } + ] + }, + "AttrEnumRef": { + "description": "A wire enum a request attribute's value is drawn from, recovered by resolving a\nstructural `o(\"Mod\").EnumName.VARIANT` reference in the builder. String-valued (all\nstanza-attr enums are wire tokens), so a consumer can generate a real enum type.", + "type": "object", + "properties": { + "module": { + "description": "The module that defines it (`WAWebBackendJobs.flow`, `WAWebUsync`, …).", + "type": "string" + }, + "name": { + "description": "The enum's name (`CiphertextType`, `USYNC_ADDRESSING_MODE`, …).", + "type": "string" + }, + "variants": { + "description": "The enum's variants in source order.", + "type": "array", + "items": { + "$ref": "#/$defs/AttrEnumVariant" + } + } + }, + "required": [ + "name", + "module", + "variants" + ] + }, + "AttrEnumVariant": { + "description": "One `NAME: \"wire_value\"` member of an [`AttrEnumRef`].", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" } + }, + "required": [ + "name", + "value" ] }, "ContentType": { @@ -68,10 +115,161 @@ "nodes" ] }, + "ErrorClass": { + "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", + "oneOf": [ + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server" + } + ] + }, + "NotifActionChild": { + "description": "A repeated sub-element an action arm maps over\n(`mapChildrenWithTag(\"participant\", …)`).", + "type": "object", + "properties": { + "fields": { + "description": "The fields read off each element.", + "type": "array", + "items": { + "$ref": "#/$defs/NotifActionField" + } + }, + "name": { + "description": "The output field the mapped list lands in (`\"participants\"`).", + "type": "string" + }, + "wireTag": { + "description": "The repeated element's wire tag (`\"participant\"`).", + "type": "string" + } + }, + "required": [ + "name", + "wireTag" + ] + }, + "NotifActionConstant": { + "description": "A `key → constant` pair an action arm stamps onto its result unconditionally.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "$ref": "#/$defs/NotifConstValue" + } + }, + "required": [ + "name", + "value" + ] + }, + "NotifActionDef": { + "description": "One arm of a notification's payload action union: a child tag of the\n`` and the fields the handler reads off it.\n\nWA Web parses these in a `switch (child.tag())` inside the handler (e.g.\n`WAWebHandleGroupNotification`), where each arm returns an object stamped with an\n`actionType`. Two facts there are invisible from the wire alone and cannot be\nderived by a consumer:\n\n1. **The mapping is many-to-one.** Several wire tags normalize onto one\n `actionType` — `ephemeral` and `not_ephemeral` both become `ephemeral`, so a\n consumer that branches on `not_ephemeral` is writing dead code.\n2. **Field names are rebound.** The disappearing-message timer arrives in the\n `expiration` attribute but the action field is called `duration` (the *create*\n payload's spelling, `ephemeralDuration`, belongs to a different shape) — reading\n it under the wrong name silently yields 0.", + "type": "object", + "properties": { + "actionType": { + "description": "The normalized action identity the handler stamps as `actionType`. **Not** always\nequal to [`wire_tag`]: `not_ephemeral` maps to `ephemeral`, `locked`/`unlocked`\nboth map to `restrict`, and so on. `None` when the arm computes it dynamically\n(e.g. `link` picks between three actions by its `link_type` attribute), which is\nrecorded rather than guessed.\n\n[`wire_tag`]: NotifActionDef::wire_tag", + "type": [ + "string", + "null" + ] + }, + "children": { + "description": "Repeated sub-elements the arm maps over (`revoke` → one entry per\n``), each with its own field list.", + "type": "array", + "items": { + "$ref": "#/$defs/NotifActionChild" + } + }, + "constantFields": { + "description": "Output fields the arm sets to a **constant** rather than reading from the wire —\n`not_ephemeral` sets `duration: 0`, `locked` sets `value: true`, `unlocked`\n`value: false`. This is exactly the normalization that is invisible from the\nstanza, and it is what makes the many-to-one tag mapping lossless: `ephemeral`\nvs `not_ephemeral` differ only by this. Sorted by key for determinism.", + "type": "array", + "items": { + "$ref": "#/$defs/NotifActionConstant" + } + }, + "fields": { + "description": "The fields the arm reads off the child node, in source order.", + "type": "array", + "items": { + "$ref": "#/$defs/NotifActionField" + } + }, + "wireTag": { + "description": "The child element's tag on the wire (`\"ephemeral\"`, `\"not_ephemeral\"`,\n`\"subject\"`, …) — the value the handler switches on.", + "type": "string" + } + }, + "required": [ + "wireTag" + ] + }, + "NotifActionField": { + "description": "One field an action arm reads off the wire.", + "type": "object", + "properties": { + "content": { + "description": "The element's text content is read instead of an attribute\n(`child(\"body\").contentString()`).", + "type": "boolean" + }, + "name": { + "description": "The output field name in the parsed action (`\"duration\"`).", + "type": "string" + }, + "required": { + "description": "Whether the arm reads the attribute unconditionally (`attrInt(\"expiration\")`) or\nguards on its presence (`hasAttr(\"reason\") ? … : null`, `maybeAttrString`).", + "type": "boolean" + }, + "type": { + "description": "How the value decodes, reusing the response-field type vocabulary.", + "$ref": "#/$defs/ParsedFieldType" + }, + "wireName": { + "description": "The wire attribute it is read from (`\"expiration\"`), or the child tag for a\ncontent read. Differs from [`name`] often enough that assuming they match is the\nbug this field exists to prevent.\n\n[`name`]: NotifActionField::name", + "type": "string" + } + }, + "required": [ + "name", + "wireName", + "type", + "required" + ] + }, + "NotifConstValue": { + "description": "The value of a [`NotifActionConstant`] — the literal kinds a handler arm actually\nstamps (`!0`/`!1`, `0`, a wire string). Deliberately narrower than [`crate::Scalar`]\n(no float) so the notif IR stays `Eq`-comparable for the determinism checks.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "string" + } + ] + }, "NotificationDef": { "description": "One arm of the inner `switch(notification.type)` — a ``\nkind and the handler that parses it.", "type": "object", "properties": { + "actions": { + "description": "The payload **action union** carried inside the notification, when its handler\ndispatches on the child tag (`w:gp2` and friends — see [`NotifActionDef`]).\n[`content`] describes the envelope; this describes what is inside it. Empty for\na notification whose handler carries no such per-child-tag switch.\n\n[`content`]: NotificationDef::content", + "type": "array", + "items": { + "$ref": "#/$defs/NotifActionDef" + } + }, "content": { "description": "The typed content shape parsed from the handler's `WADeprecatedWapParser`\n(Phase 2). `None` when the handler carries no statically-recoverable parser\n(it delegates to a job/sub-module) — the catalog entry still stands.", "anyOf": [ @@ -171,6 +369,17 @@ "type": "string" } }, + "enumRef": { + "description": "The wire enum this field's value is drawn from, when the parser reads it with an\nenum accessor (`attrStringEnum(node, \"state\", o(\"Mod\").ENUM_OFF_ON)`). Resolved\nthe same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer\ncan type the field as that enum instead of a bare string — and an emitter knows\nwhich values are legal instead of guessing from the generated `ENUM_OFF_ON` name.\nAbsent when the enum argument is not a structurally resolvable module export, or\nresolves to a non-string enum (never guessed).\n\n[`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "intMax": { "description": "Inclusive upper bound from the same `attrIntRange` check (see [`int_min`]).\n\n[`int_min`]: ParsedField::int_min", "type": [ @@ -187,6 +396,13 @@ ], "format": "int64" }, + "literalValue": { + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "type": [ + "string", + "null" + ] + }, "method": { "type": "string" }, @@ -194,6 +410,16 @@ "description": "The output (struct field) name — the camelCase `makeResult` key in smax.", "type": "string" }, + "referencePath": { + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "repeats": { "type": [ "boolean", @@ -385,6 +611,16 @@ "null" ] }, + "referencePath": { + "description": "Where the expected value is read from **in the request**, for\n[`AssertionKind::Reference`]. It is the argument list of WA's\n`attrStringFromReference(request, path)` helper: every element but the last is a\nchild tag to descend into, and the last is the attribute name. So `[\"to\"]` means\n\"the request's `to` attribute\" and `[\"account\", \"action\"]` means \"the `action`\nattribute of the request's `` child\".\n\nCarried structurally (rather than left to be inferred from `name`) so a consumer\nnever has to pattern-match attribute names to know an echo rule applies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "value": { "type": [ "string", @@ -406,6 +642,48 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorClass": { + "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorClass" + }, + { + "type": "null" + } + ] + }, + "errorCodeMax": { + "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodeMin": { + "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodes": { + "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "errorTexts": { + "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "type": "array", + "items": { + "type": "string" + } + }, "fields": { "type": "array", "items": { diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 6300ab0..112fc65 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -40,7 +40,54 @@ "description": "The node's text content is pinned to a fixed value (`literalContent(content,\nnode, \"admin_add\")`) — a discriminator for marker union variants. The value is\nin [`ResponseAssertion::value`]; `name` is unused.", "type": "string", "const": "content" + }, + { + "description": "The attribute named by [`ResponseAssertion::name`] must **echo a value taken\nfrom the request** — `literal(attrString, node, \"from\",\nattrStringFromReference(request, [\"to\"]))`. The expected value is not a\nconstant, so [`ResponseAssertion::value`] is absent; where it comes from is in\n[`ResponseAssertion::reference_path`].\n\nThis is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success\nparser) enforce: an answer's `id` must equal the request's `id` and its `from`\nthe request's `to`. An emitter that hardcodes `from=\"s.whatsapp.net\"` produces\nstanzas a real client cannot parse whenever the request went to `g.us`.", + "type": "string", + "const": "reference" + } + ] + }, + "AttrEnumRef": { + "description": "A wire enum a request attribute's value is drawn from, recovered by resolving a\nstructural `o(\"Mod\").EnumName.VARIANT` reference in the builder. String-valued (all\nstanza-attr enums are wire tokens), so a consumer can generate a real enum type.", + "type": "object", + "properties": { + "module": { + "description": "The module that defines it (`WAWebBackendJobs.flow`, `WAWebUsync`, …).", + "type": "string" + }, + "name": { + "description": "The enum's name (`CiphertextType`, `USYNC_ADDRESSING_MODE`, …).", + "type": "string" + }, + "variants": { + "description": "The enum's variants in source order.", + "type": "array", + "items": { + "$ref": "#/$defs/AttrEnumVariant" + } } + }, + "required": [ + "name", + "module", + "variants" + ] + }, + "AttrEnumVariant": { + "description": "One `NAME: \"wire_value\"` member of an [`AttrEnumRef`].", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" ] }, "ContentType": { @@ -52,6 +99,21 @@ "nodes" ] }, + "ErrorClass": { + "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", + "oneOf": [ + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server" + } + ] + }, "ParsedField": { "description": "One field extracted from a response stanza by a parser.\n\n`method` is the parser accessor used (e.g. `attrString`, `maybeChild`,\n`forEachChildWithTag`) and is left open-ended (free-form string) to track the\nreal WA accessor surface; the codegen switches on it.", "type": "object", @@ -110,6 +172,17 @@ "type": "string" } }, + "enumRef": { + "description": "The wire enum this field's value is drawn from, when the parser reads it with an\nenum accessor (`attrStringEnum(node, \"state\", o(\"Mod\").ENUM_OFF_ON)`). Resolved\nthe same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer\ncan type the field as that enum instead of a bare string — and an emitter knows\nwhich values are legal instead of guessing from the generated `ENUM_OFF_ON` name.\nAbsent when the enum argument is not a structurally resolvable module export, or\nresolves to a non-string enum (never guessed).\n\n[`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "intMax": { "description": "Inclusive upper bound from the same `attrIntRange` check (see [`int_min`]).\n\n[`int_min`]: ParsedField::int_min", "type": [ @@ -126,6 +199,13 @@ ], "format": "int64" }, + "literalValue": { + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "type": [ + "string", + "null" + ] + }, "method": { "type": "string" }, @@ -133,6 +213,16 @@ "description": "The output (struct field) name — the camelCase `makeResult` key in smax.", "type": "string" }, + "referencePath": { + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "repeats": { "type": [ "boolean", @@ -324,6 +414,16 @@ "null" ] }, + "referencePath": { + "description": "Where the expected value is read from **in the request**, for\n[`AssertionKind::Reference`]. It is the argument list of WA's\n`attrStringFromReference(request, path)` helper: every element but the last is a\nchild tag to descend into, and the last is the attribute name. So `[\"to\"]` means\n\"the request's `to` attribute\" and `[\"account\", \"action\"]` means \"the `action`\nattribute of the request's `` child\".\n\nCarried structurally (rather than left to be inferred from `name`) so a consumer\nnever has to pattern-match attribute names to know an echo rule applies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "value": { "type": [ "string", @@ -345,6 +445,48 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorClass": { + "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorClass" + }, + { + "type": "null" + } + ] + }, + "errorCodeMax": { + "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodeMin": { + "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodes": { + "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "errorTexts": { + "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "type": "array", + "items": { + "type": "string" + } + }, "fields": { "type": "array", "items": { diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index 31e3ed5..c700ff0 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -39,6 +39,11 @@ "description": "The node's text content is pinned to a fixed value (`literalContent(content,\nnode, \"admin_add\")`) — a discriminator for marker union variants. The value is\nin [`ResponseAssertion::value`]; `name` is unused.", "type": "string", "const": "content" + }, + { + "description": "The attribute named by [`ResponseAssertion::name`] must **echo a value taken\nfrom the request** — `literal(attrString, node, \"from\",\nattrStringFromReference(request, [\"to\"]))`. The expected value is not a\nconstant, so [`ResponseAssertion::value`] is absent; where it comes from is in\n[`ResponseAssertion::reference_path`].\n\nThis is the rule 17 of the 26 namespace `IQError…Mixin`s (and every success\nparser) enforce: an answer's `id` must equal the request's `id` and its `from`\nthe request's `to`. An emitter that hardcodes `from=\"s.whatsapp.net\"` produces\nstanzas a real client cannot parse whenever the request went to `g.us`.", + "type": "string", + "const": "reference" } ] }, @@ -101,6 +106,21 @@ "incoming" ] }, + "ErrorClass": { + "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", + "oneOf": [ + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server" + } + ] + }, "ParsedField": { "description": "One field extracted from a response stanza by a parser.\n\n`method` is the parser accessor used (e.g. `attrString`, `maybeChild`,\n`forEachChildWithTag`) and is left open-ended (free-form string) to track the\nreal WA accessor surface; the codegen switches on it.", "type": "object", @@ -159,6 +179,17 @@ "type": "string" } }, + "enumRef": { + "description": "The wire enum this field's value is drawn from, when the parser reads it with an\nenum accessor (`attrStringEnum(node, \"state\", o(\"Mod\").ENUM_OFF_ON)`). Resolved\nthe same way the request side resolves [`WapAttrDef::enum_ref`], so a consumer\ncan type the field as that enum instead of a bare string — and an emitter knows\nwhich values are legal instead of guessing from the generated `ENUM_OFF_ON` name.\nAbsent when the enum argument is not a structurally resolvable module export, or\nresolves to a non-string enum (never guessed).\n\n[`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "intMax": { "description": "Inclusive upper bound from the same `attrIntRange` check (see [`int_min`]).\n\n[`int_min`]: ParsedField::int_min", "type": [ @@ -175,6 +206,13 @@ ], "format": "int64" }, + "literalValue": { + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "type": [ + "string", + "null" + ] + }, "method": { "type": "string" }, @@ -182,6 +220,16 @@ "description": "The output (struct field) name — the camelCase `makeResult` key in smax.", "type": "string" }, + "referencePath": { + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "repeats": { "type": [ "boolean", @@ -373,6 +421,16 @@ "null" ] }, + "referencePath": { + "description": "Where the expected value is read from **in the request**, for\n[`AssertionKind::Reference`]. It is the argument list of WA's\n`attrStringFromReference(request, path)` helper: every element but the last is a\nchild tag to descend into, and the last is the attribute name. So `[\"to\"]` means\n\"the request's `to` attribute\" and `[\"account\", \"action\"]` means \"the `action`\nattribute of the request's `` child\".\n\nCarried structurally (rather than left to be inferred from `name`) so a consumer\nnever has to pattern-match attribute names to know an echo rule applies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "value": { "type": [ "string", @@ -394,6 +452,48 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorClass": { + "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorClass" + }, + { + "type": "null" + } + ] + }, + "errorCodeMax": { + "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodeMin": { + "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "errorCodes": { + "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "errorTexts": { + "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "type": "array", + "items": { + "type": "string" + } + }, "fields": { "type": "array", "items": { diff --git a/generated/srvreq/index.json b/generated/srvreq/index.json index 1cfc2a3..60e3dd3 100644 --- a/generated/srvreq/index.json +++ b/generated/srvreq/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "requests": [ { @@ -38,7 +38,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "business" }, { "method": "attrString", @@ -96,6 +97,20 @@ "wireName": "display", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_INFO_WARNING", + "module": "WASmaxInBizCtwaActionEnums", + "variants": [ + { + "name": "info", + "value": "info" + }, + { + "name": "warning", + "value": "warning" + } + ] + }, "sourcePath": [ "config" ] @@ -106,6 +121,20 @@ "wireName": "revoked", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInBizCtwaActionEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "config" ] @@ -468,7 +497,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "business" }, { "method": "contentString", @@ -541,7 +571,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "business" }, { "method": "child", @@ -556,6 +587,24 @@ "wireName": "value", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_NOTSET_TRUE", + "module": "WASmaxInBizSettingsEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "notset", + "value": "notset" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "smb_data_sharing_with_meta_consent" ] @@ -628,7 +677,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "hosted" }, { "method": "attrEnum", @@ -636,6 +686,24 @@ "wireName": "product_surface", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AIFROMMETA_AUTOMATION_BUSINESSPLATFORM", + "module": "WASmaxInCoexistenceEnums", + "variants": [ + { + "name": "ai_from_meta", + "value": "ai_from_meta" + }, + { + "name": "automation", + "value": "automation" + }, + { + "name": "business_platform", + "value": "business_platform" + } + ] + }, "sourcePath": [ "offboarding" ] @@ -762,7 +830,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "hosted" }, { "method": "attrEnum", @@ -770,6 +839,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_COMPLETED_FAILED", + "module": "WASmaxInCoexistenceEnums", + "variants": [ + { + "name": "completed", + "value": "completed" + }, + { + "name": "failed", + "value": "failed" + } + ] + }, "sourcePath": [ "onboarding_status" ] @@ -786,7 +869,25 @@ "name": "productSurface", "wireName": "product_surface", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ENUM_AIFROMMETA_AUTOMATION_BUSINESSPLATFORM", + "module": "WASmaxInCoexistenceEnums", + "variants": [ + { + "name": "ai_from_meta", + "value": "ai_from_meta" + }, + { + "name": "automation", + "value": "automation" + }, + { + "name": "business_platform", + "value": "business_platform" + } + ] + } } ], "repeats": false @@ -906,7 +1007,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "w:gp2" }, { "method": "attrJidWithType", @@ -983,7 +1085,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "passkey_prologue_request" }, { "method": "attrJidWithType", @@ -1058,7 +1161,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "link_code_companion_reg" }, { "method": "attrJidWithType", @@ -1073,6 +1177,7 @@ "wireName": "stage", "type": "string", "required": true, + "literalValue": "primary_hello", "sourcePath": [ "link_code_companion_reg" ] @@ -1155,7 +1260,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "link_code_companion_reg" }, { "method": "attrJidWithType", @@ -1170,6 +1276,7 @@ "wireName": "stage", "type": "string", "required": true, + "literalValue": "refresh_code", "sourcePath": [ "link_code_companion_reg" ] @@ -1180,6 +1287,20 @@ "wireName": "force_manual_refresh", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInMdEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "link_code_companion_reg" ] @@ -1242,7 +1363,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "crsc_continuation" }, { "method": "attrJidWithType", @@ -1359,7 +1481,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -1494,6 +1617,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -1510,6 +1634,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1549,7 +1674,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -1563,6 +1689,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1592,6 +1719,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -1619,7 +1832,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -1633,6 +1847,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1652,6 +1867,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -1709,7 +1958,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -1723,6 +1973,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1745,6 +1996,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -1761,6 +2013,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1800,7 +2053,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -1814,6 +2068,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1843,6 +2098,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -1870,7 +2211,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -1884,6 +2226,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -1903,6 +2246,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -1978,7 +2355,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -1992,6 +2370,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2021,6 +2400,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -2048,7 +2513,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -2062,6 +2528,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2081,6 +2548,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -2144,6 +2645,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -2160,6 +2662,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2199,7 +2702,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -2213,6 +2717,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2242,6 +2747,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -2269,7 +2860,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -2283,6 +2875,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2302,6 +2895,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -2359,14 +2986,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -2380,6 +3009,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2431,7 +3061,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -2445,6 +3076,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2474,6 +3106,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -2501,7 +3219,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -2515,6 +3234,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2534,6 +3254,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInNewslettersEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -2599,7 +3353,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -2607,6 +3362,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -2633,6 +3389,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2679,7 +3436,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -2687,6 +3445,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -2713,6 +3472,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2759,7 +3519,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -2767,6 +3528,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -2783,6 +3545,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2812,6 +3575,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -2827,7 +3591,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -2843,7 +3608,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -2863,6 +3629,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -2881,7 +3648,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -2904,7 +3672,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -2938,6 +3707,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -2956,7 +3726,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -2979,7 +3750,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -3012,7 +3784,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -3035,7 +3808,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -3058,7 +3832,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -3066,6 +3841,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -3101,7 +3877,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -3109,6 +3886,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -3251,6 +4029,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -3269,7 +4048,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -3314,7 +4094,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "views" }, { "method": "attrInt", @@ -3378,7 +4159,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "newsletter" }, { "method": "attrInt", @@ -3435,7 +4217,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "psa" }, { "method": "attrInt", @@ -3492,7 +4275,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "psa" }, { "method": "child", @@ -3600,6 +4384,20 @@ "wireName": "dismissable", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInQpSurfacesEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "qp_config" ] @@ -3610,6 +4408,20 @@ "wireName": "force_pass", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInQpSurfacesEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "qp_config" ] @@ -3630,6 +4442,20 @@ "wireName": "deterministic", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInQpSurfacesEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "qp_config" ] @@ -3650,6 +4476,20 @@ "wireName": "exposure_holdout", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInQpSurfacesEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "qp_config" ] @@ -3700,6 +4540,20 @@ "wireName": "log_eligibility_waterfall", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInQpSurfacesEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "qp_config" ] @@ -4135,7 +4989,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "business" }, { "method": "maybeAttrString", @@ -4173,6 +5028,20 @@ "wireName": "status", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_INTEGRITYNOTCLEARED_OK", + "module": "WASmaxInSmbMeteredMessagesCampaignEnums", + "variants": [ + { + "name": "INTEGRITY_NOT_CLEARED", + "value": "INTEGRITY_NOT_CLEARED" + }, + { + "name": "OK", + "value": "OK" + } + ] + }, "sourcePath": [ "mm_campaign" ] @@ -4232,7 +5101,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "waffle" }, { "method": "attrInt", @@ -4252,6 +5122,20 @@ "wireName": "show_user_notif", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] @@ -4274,6 +5158,20 @@ "wireName": "client_resync", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] @@ -4294,6 +5192,20 @@ "wireName": "npr", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInWaffleEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "notification_metadata" ] @@ -4358,7 +5270,8 @@ "name": "xmlns", "wireName": "xmlns", "type": "string", - "required": true + "required": true, + "literalValue": "md" }, { "method": "contentBytes", @@ -4400,6 +5313,20 @@ "wireName": "beta", "type": "enum", "required": false, + "enumRef": { + "name": "ENUM_FALSE_TRUE", + "module": "WASmaxInMdEnums", + "variants": [ + { + "name": "false", + "value": "false" + }, + { + "name": "true", + "value": "true" + } + ] + }, "sourcePath": [ "pair-success", "device" @@ -4428,7 +5355,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "set" }, { "method": "child", @@ -4481,14 +5409,16 @@ "name": "version", "wireName": "version", "type": "string", - "required": true + "required": true, + "literalValue": "1" }, { "method": "attrString", "name": "algorithm", "wireName": "algorithm", "type": "string", - "required": true + "required": true, + "literalValue": "aes-256-gcm" }, { "method": "contentBytes", @@ -4577,7 +5507,8 @@ "name": "xmlns", "wireName": "xmlns", "type": "string", - "required": true + "required": true, + "literalValue": "md" }, { "method": "attrString", @@ -4591,7 +5522,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "set" }, { "method": "child", @@ -4707,7 +5639,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "unavailable" } ], "assertions": [ @@ -4737,14 +5670,33 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "unavailable" }, { "method": "maybeAttrEnum", "name": "last", "wireName": "last", "type": "enum", - "required": false + "required": false, + "enumRef": { + "name": "ENUM_DENY_ERROR_NONE", + "module": "WASmaxInPresenceEnums", + "variants": [ + { + "name": "deny", + "value": "deny" + }, + { + "name": "error", + "value": "error" + }, + { + "name": "none", + "value": "none" + } + ] + } } ], "assertions": [ @@ -4774,7 +5726,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "unavailable" }, { "method": "maybeAttrString", @@ -4811,7 +5764,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": false + "required": false, + "literalValue": "available" }, { "method": "maybeAttrString", @@ -4937,6 +5891,7 @@ "wireName": "media", "type": "string", "required": false, + "literalValue": "audio", "sourcePath": [ "composing" ] @@ -5005,7 +5960,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -5140,6 +6096,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -5156,6 +6113,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5195,7 +6153,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -5209,6 +6168,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5238,6 +6198,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -5265,7 +6311,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -5279,6 +6326,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5298,6 +6346,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -5373,7 +6455,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "attrString", @@ -5381,6 +6464,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "response", "sourcePath": [ "meta" ] @@ -5416,7 +6500,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" }, { "method": "", @@ -5430,6 +6515,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5452,6 +6538,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -5468,6 +6555,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5507,7 +6595,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -5521,6 +6610,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5550,6 +6640,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -5577,7 +6753,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -5591,6 +6768,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5610,6 +6788,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -5685,7 +6897,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -5699,6 +6912,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5728,6 +6942,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -5755,7 +7055,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -5769,6 +7070,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5788,6 +7090,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -5851,6 +7187,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -5867,6 +7204,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5906,7 +7244,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -5920,6 +7259,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -5949,6 +7289,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -5976,7 +7402,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -5990,6 +7417,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6009,6 +7437,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -6066,14 +7528,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -6087,6 +7551,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6138,7 +7603,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" }, { "method": "", @@ -6152,6 +7618,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6181,6 +7648,92 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_1PSTICKER_AUDIO_AVATARSTICKER_CATALOGLINK_COLLECTION_DOCUMENT_GENAISTICKER_GIF_IMAGE_MOTIONPHOTO_MOTIONVIDEO_PRODUCTLINK_PTT_PTV_STICKER_STICKERPACK_URL_USERCREATEDSTICKER_VCARD_VIDEO", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "1p_sticker", + "value": "1p_sticker" + }, + { + "name": "audio", + "value": "audio" + }, + { + "name": "avatar_sticker", + "value": "avatar_sticker" + }, + { + "name": "cataloglink", + "value": "cataloglink" + }, + { + "name": "collection", + "value": "collection" + }, + { + "name": "document", + "value": "document" + }, + { + "name": "genai_sticker", + "value": "genai_sticker" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "motion_video", + "value": "motion_video" + }, + { + "name": "productlink", + "value": "productlink" + }, + { + "name": "ptt", + "value": "ptt" + }, + { + "name": "ptv", + "value": "ptv" + }, + { + "name": "sticker", + "value": "sticker" + }, + { + "name": "sticker_pack", + "value": "sticker_pack" + }, + { + "name": "url", + "value": "url" + }, + { + "name": "user_created_sticker", + "value": "user_created_sticker" + }, + { + "name": "vcard", + "value": "vcard" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -6208,7 +7761,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" }, { "method": "", @@ -6222,6 +7776,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6241,6 +7796,40 @@ "wireName": "message_association_type", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_HDIMAGEDUALUPLOAD_HDVIDEODUALUPLOAD_HEVCVIDEODUALUPLOAD_MEDIAPOLL_MOTIONPHOTO_POLLADDOPTION_STICKERANNOTATION", + "module": "WASmaxInMessageDeliverEnums", + "variants": [ + { + "name": "hd_image_dual_upload", + "value": "hd_image_dual_upload" + }, + { + "name": "hd_video_dual_upload", + "value": "hd_video_dual_upload" + }, + { + "name": "hevc_video_dual_upload", + "value": "hevc_video_dual_upload" + }, + { + "name": "media_poll", + "value": "media_poll" + }, + { + "name": "motion_photo", + "value": "motion_photo" + }, + { + "name": "poll_add_option", + "value": "poll_add_option" + }, + { + "name": "sticker_annotation", + "value": "sticker_annotation" + } + ] + }, "sourcePath": [ "meta" ] @@ -6298,7 +7887,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -6321,14 +7911,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -6374,7 +7966,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6382,6 +7975,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -6408,6 +8002,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6454,7 +8049,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6462,6 +8058,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -6488,6 +8085,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6516,7 +8114,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6524,6 +8123,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "vote", "sourcePath": [ "meta" ] @@ -6597,7 +8197,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6605,6 +8206,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "result_snapshot", "sourcePath": [ "meta" ] @@ -6621,6 +8223,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6650,6 +8253,7 @@ "wireName": "is_wamo_sub", "type": "string", "required": true, + "literalValue": "true", "sourcePath": [ "meta" ] @@ -6665,7 +8269,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" } ], "sameNode": true @@ -6681,7 +8286,8 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "3" } ], "sameNode": true @@ -6701,6 +8307,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "question", "sourcePath": [ "meta" ] @@ -6719,7 +8326,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -6742,7 +8350,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -6776,6 +8385,7 @@ "wireName": "questiontype", "type": "string", "required": true, + "literalValue": "reply", "sourcePath": [ "meta" ] @@ -6794,7 +8404,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -6817,7 +8428,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -6850,7 +8462,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -6873,7 +8486,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -6896,7 +8510,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6904,6 +8519,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "creation", "sourcePath": [ "meta" ] @@ -6939,7 +8555,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "poll" }, { "method": "attrString", @@ -6947,6 +8564,7 @@ "wireName": "polltype", "type": "string", "required": true, + "literalValue": "quiz_creation", "sourcePath": [ "meta" ] @@ -7034,6 +8652,7 @@ "wireName": "mediatype", "type": "string", "required": true, + "literalValue": "url", "sourcePath": [ "plaintext" ] @@ -7052,7 +8671,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "sameNode": true @@ -7107,7 +8727,8 @@ "name": "isSender", "wireName": "is_sender", "type": "string", - "required": false + "required": false, + "literalValue": "true" }, { "method": "", @@ -7123,14 +8744,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "8" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -7176,7 +8799,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "text" } ], "assertions": [ @@ -7200,6 +8824,28 @@ "wireName": "mediatype", "type": "enum", "required": true, + "enumRef": { + "name": "ENUM_AUDIO_GIF_IMAGE_VIDEO", + "module": "WASmaxInStatusDeliverEnums", + "variants": [ + { + "name": "audio", + "value": "audio" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "image", + "value": "image" + }, + { + "name": "video", + "value": "video" + } + ] + }, "sourcePath": [ "plaintext" ] @@ -7227,7 +8873,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "media" } ], "assertions": [ @@ -7260,7 +8907,8 @@ "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ @@ -7283,14 +8931,16 @@ "name": "edit", "wireName": "edit", "type": "string", - "required": true + "required": true, + "literalValue": "7" }, { "method": "attrString", "name": "type", "wireName": "type", "type": "string", - "required": true + "required": true, + "literalValue": "reaction" } ], "assertions": [ diff --git a/generated/stanza/index.json b/generated/stanza/index.json index 779abce..140037e 100644 --- a/generated/stanza/index.json +++ b/generated/stanza/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "stanzas": [ { diff --git a/generated/tokens/index.json b/generated/tokens/index.json index ff0a9d7..5fbe3c6 100644 --- a/generated/tokens/index.json +++ b/generated/tokens/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "dictVersion": 3, "singleByte": [ diff --git a/generated/wam/index.json b/generated/wam/index.json index d695609..95d28fd 100644 --- a/generated/wam/index.json +++ b/generated/wam/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "events": [ { diff --git a/generated/wasm/index.json b/generated/wasm/index.json index 2d4a769..770ec51 100644 --- a/generated/wasm/index.json +++ b/generated/wasm/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.0.0", + "schemaVersion": "1.1.0", "waVersion": "2.3000.1043899084", "binaries": [ { From 55001157e9ab96e33046813612e1f1bf62742cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 17:24:18 -0300 Subject: [PATCH 02/37] scan: enforce that a reference echo reads the request, not just any node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `referencePath` is documented as relative to the request, and a consumer acts on that. The extractor keyed only on the binding being a `WASmaxParseReference` helper, so it was reporting an observed property as if it were a guaranteed one: if WA ever pointed one of those helpers at a node other than the parser's `reference` parameter, the IR would still say "the request" and every consumer would be silently wrong. The reference argument is now checked against the parser's second parameter and rejected otherwise, with the rejection counted under dropsByReason rather than dropped in silence. The invariant already held: coverage is unchanged at 579 reference constraints, no new drop reason appears, and generated/ is byte-identical. That is the point — it is now enforced instead of assumed. Also groups the per-function analysis context (locals, resolver, reference parameter) into `FnCtx` so the classifier keeps a readable signature. docs: a cache filename is a location label, not a content address The durable release store is content-addressed (`bundles--`), the cache is not: a JS entry is named after the URL's last segment and a wasm entry after `sha256(url)`, while the hash of the *bytes* lives in the cache manifest and is what integrity is verified against. Two hashes of different things sat three lines apart in the README with nothing saying so, which is enough to cost a reader time even though no sentence was false. --- README.md | 4 +- crates/wa-fetch/src/cache.rs | 8 +++ crates/wa-scan/src/response_smax.rs | 105 +++++++++++++++++++++++++--- 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7a1acd8..b438677 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ cargo run --release -p whatspec -- update --cache .wa-cache # Seed that same cache from the lock-pinned GitHub Release (no live bundle fetch): cargo run --release -p whatspec -- restore --from-lock generated/bundles.lock.json --cache .wa-cache -# Also resolve, download and store the client's wasm payloads (~41 MB), as .wasm: +# Also resolve, download and store the client's wasm payloads (~41 MB), as .wasm +# (a cache filename is a location label, not a content address — see the note below): cargo run --release -p whatspec -- update --cache .wa-cache --wasm-out ./wasm # Restore a locked wasm set (its own lock + its own content-addressed release asset): @@ -68,6 +69,7 @@ WhatsApp only serves the *current* bundle version — old bundle URLs 404 — so - **`generated/bundles.lock.json`** records the content SHA-256 (+ size, and origin URL when known) of every bundle that produced the committed `generated/`, plus a one-line, order-invariant `setHash` fingerprint of the whole set. - The **bytes** live in a durable, WhatsApp-independent store: a rolling **`bundle-store`** GitHub Release whose assets are **content-addressed** — `bundles--.tar.xz` (pure-Rust xz, roughly half the size of gzip; legacy `.tar.gz` assets are still read) — so a set is never overwritten with different bytes and every past commit's lock keeps resolving the exact archive it pins. Published automatically by the update workflow. + Note the two different hashes: a **release asset** is named after its *contents* (`setHash`), while a **cache** filename is named after its *URL* (the JS last segment, or `sha256(url)` for wasm) — there, the content hash lives in the cache's `manifest.json` and is what integrity is verified against. A cache filename never proves what is inside the file. - **`whatspec restore --from-lock generated/bundles.lock.json --out `** pulls that archive, verifies every bundle's SHA-256 against the lock, and writes a directory ready for `update --bundles`. Use **`--cache `** instead to seed the exact version directly into the reusable `update --cache` layout; cache metadata and integrity files are written by the same `BundleCache` implementation as a live fetch. **`scripts/regen.sh`** wraps restore + `update --check` into a one-shot, offline determinism check — also run in CI, so every commit's committed IR (each `index.json` + `WAProto.proto`) is proven reproducible from its pinned inputs. > Bootstrap: the lock and the first archive are created by the first run of the update workflow (or a manual `update --save-bundles ` followed by `scripts/publish-bundles.sh `). Until then the CI reproducibility gate stays dormant. diff --git a/crates/wa-fetch/src/cache.rs b/crates/wa-fetch/src/cache.rs index ec9817a..f204f82 100644 --- a/crates/wa-fetch/src/cache.rs +++ b/crates/wa-fetch/src/cache.rs @@ -15,6 +15,14 @@ //! … //! ``` //! +//! The cache is **not** content-addressed, unlike the durable release store it can be +//! seeded from (`bundles--.tar.xz`). A cache filename is a *location* +//! label — the JS name comes from the URL's last segment, the wasm name is +//! `sha256(url)` — while the `sha256` recorded in `manifest.json` is the hash of the +//! **bytes**, and that is what integrity is checked against. Two hashes of different +//! things sit side by side here, so: the name never proves the content, the manifest +//! does. +//! //! JS and wasm live in **separate subtrees and separate manifest lists** on purpose: //! the JS set is concatenated into the source the extractors parse, so a wasm payload //! that leaked into that list would put megabytes of binary into it. [`BundleCache::check`] diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index adc4e7c..ff6179c 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -383,6 +383,20 @@ fn analyze_function( visited: &mut HashSet, ) -> Option<(Vec, Vec)> { let body = func.body.as_ref()?; + // A response parser's signature is `parse…(node, reference)`: the second parameter + // is the REQUEST the answer is correlated against. Every echo rule is relative to + // it, so it is threaded down and a `…FromReference` call on any other node is + // rejected rather than silently reported as "the request" (see [`classify_reference`]). + let reference_param = func + .params + .items + .get(1) + .and_then(|p| p.pattern.get_identifier_name()); + let ctx = FnCtx { + locals, + resolver, + reference_param: reference_param.as_deref(), + }; // Same-node cross-module mixins bubble their discriminators up by default (so an // error variant inherits the `type:"error"` its mixin asserts). But a mixin whose // result the tail consumes *optionally* (`X.success ? X.value : null`) does not @@ -413,8 +427,7 @@ fn analyze_function( let b = classify_call( init, &mut assertions, - locals, - resolver, + &ctx, visited, &bindings, suppressed.contains(name.as_str()), @@ -436,19 +449,33 @@ fn analyze_function( Some((assertions, fields)) } +/// The immutable context one parse function is analyzed under: its module's local +/// parsers, the cross-module resolver, and the name of its `reference` parameter. +/// Grouped so the per-binding classifier keeps a readable signature. +struct FnCtx<'c> { + locals: &'c LocalFns, + resolver: &'c Resolver<'c>, + /// The parser's `reference` parameter — the request an echo rule is relative to. + reference_param: Option<&'c str>, +} + /// Classify the RHS of a railway binding into a [`Binding`], recording any /// assertion it implies (assertTag/assertAttr/literal…). fn classify_call( init: &Expression, assertions: &mut Vec, - locals: &LocalFns, - resolver: &Resolver, + ctx: &FnCtx, visited: &mut HashSet, bindings: &HashMap, // Whether this binding's var is consumed optionally by the tail, so a same-node // mixin it binds must not bubble its discriminators (see `analyze_function`). suppress_bubble: bool, ) -> Binding { + let FnCtx { + resolver, + reference_param, + .. + } = *ctx; let Some(call) = as_call(init) else { return Binding::None; }; @@ -657,7 +684,9 @@ fn classify_call( | "optionalAttrStringFromReference" | "attrFromReference" | "optionalAttrFromReference" - | "contentStringFromReference" => classify_reference(method, args, resolver), + | "contentStringFromReference" => { + classify_reference(method, args, resolver, reference_param) + } // `optional(ACCESSOR, node, …)` → the wrapped accessor decides the type; // required = false. "optional" => { @@ -695,7 +724,7 @@ fn classify_call( | "optionalChild" | "optionalChildWithTag" | "flattenedChildWithTag" - | "mapChildrenWithTag" => classify_child(method, args, locals, resolver, visited, bindings), + | "mapChildrenWithTag" => classify_child(method, args, ctx, visited, bindings), other => match normalize_accessor(other) { Some((m, ft, bl)) => { let (field_type, int_range) = int_range_and_type(Some(other), args, ft); @@ -761,11 +790,36 @@ fn reference_path_of(e: &Expression, bindings: &HashMap) -> Opt /// /// The path argument is the helper's own contract: every element but the last is a /// child tag to descend in the request, the last is the attribute read there. -fn classify_reference(method: &str, args: &[Argument], resolver: &Resolver) -> Binding { - let Some(path) = args.iter().find_map(|a| arg_expr(a).and_then(string_array)) else { +fn classify_reference( + method: &str, + args: &[Argument], + resolver: &Resolver, + reference_param: Option<&str>, +) -> Binding { + let Some(path_idx) = args + .iter() + .position(|a| arg_expr(a).is_some_and(|e| string_array(e).is_some())) + else { resolver.drop_note("reference path argument not statically resolvable"); return Binding::None; }; + let path = arg_expr(&args[path_idx]) + .and_then(string_array) + .expect("position matched"); + // The node the path is walked from is the identifier argument just before it. It + // MUST be the parser's reference parameter: `referencePath` is documented as + // "relative to the request", and a consumer acting on it would be silently wrong if + // WA ever pointed a `…FromReference` helper at some other node. Enforce the + // invariant instead of trusting it — and count the rejection rather than dropping + // it silently, so the day it happens is visible. + let node = args[..path_idx] + .iter() + .rev() + .find_map(|a| arg_expr(a).and_then(as_identifier)); + if node.is_none() || node != reference_param { + resolver.drop_note("reference read from a node other than the request"); + return Binding::None; + } // An explicit accessor (the `attrFromReference` family) decides the type; the // `attrString…` spelling is fixed to a string. let (base_method, field_type) = match method { @@ -874,11 +928,13 @@ fn node_descend_path( fn classify_child( method: &str, args: &oxc_allocator::Vec, - locals: &LocalFns, - resolver: &Resolver, + ctx: &FnCtx, visited: &mut HashSet, bindings: &HashMap, ) -> Binding { + let FnCtx { + locals, resolver, .. + } = *ctx; // The wire tag is the first string-literal arg. let Some(tag_idx) = args .iter() @@ -1957,6 +2013,35 @@ mod tests { assert_eq!(ty.literal_value.as_deref(), Some("error")); } + #[test] + fn a_reference_off_a_node_other_than_the_request_is_rejected() { + // `referencePath` is documented as relative to the REQUEST, and a consumer acts + // on that. If WA ever pointed a `…FromReference` helper at some other node, an + // echo rule read as "the request's" would be silently wrong — so the invariant + // is enforced, and the rejection is counted rather than dropped in silence. + let slices = HashMap::new(); + let resolver = Resolver::new(&slices); + // `node` is the FIRST parameter (the response), not the reference. + let body = r#"function e(node, ref){ + var r = o("WASmaxParseReference").attrStringFromReference(node, ["id"]); if(!r.success) return r; + var a = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, node, "id", r.value); + return o("WAResultOrError").makeResult({ id: a.value }); + }"#; + let (asserts, _f) = + analyze_fn_source(body, &LocalFns::new(), &resolver, &mut HashSet::new()) + .expect("analyzed"); + assert!( + !asserts.iter().any(|a| a.kind == AssertionKind::Reference), + "an echo off a non-request node must not be reported as a request echo" + ); + assert_eq!( + resolver + .drop_counts() + .get("reference read from a node other than the request"), + Some(&1) + ); + } + #[test] fn multi_hop_reference_path_is_kept_whole() { // `attrStringFromReference(request, ["account","action"])` reads the `action` From 08c01ed31436c8b4f80c89c2ebb0ec057657bc64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 17:39:35 -0300 Subject: [PATCH 03/37] review: fix the constraint-layer defects found in review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sixteen of the seventeen review findings applied; one rejected with evidence. The four that changed committed output were all verified against the bundle first. Wrong data in the committed IR: - `iq_constraint_counts` walked `response.fields` AND every variant, but `response.fields` is a *clone* of the primary success variant's fields whenever variants exist — so 97 of 143 stanzas had their constraints counted twice, and the inflation wasn't even uniform. fieldLiterals 2174 -> 1782 and fieldEnumRefs 277 -> 146 are the corrected totals, not a regression: counting the OLD IR with the fixed logic gives exactly the same numbers. `--allow-shrink` was required once to move past the inflated floor. - The error vocabulary was read only from fields inside a `UnionVariant`, but plenty of RPCs parse a single `` child directly instead of an `errorMixinDisjunction`. 29 error variants silently had no vocabulary: `SetResponseConflict` (409/conflict), `…TooManyAttempts` (431), and so on. The code/text reading now applies to every traversed field. - Conversely, the vocabulary was computed for EVERY variant kind and only `error_class` was gated on `Error`, so three PreKeys *success* variants carried a bogus accepted-error set. The whole computation is now gated. - An optional echo consumed as a presence flag lost its rule entirely. The blocklist shapes bind `optionalLiteral(attrString, list, "c_dhash", ref.item.dhash)` and expose only `{hasListCDhash: m.value != null}`, so the pin was dropped with no field and no drop note — worse than before, since the previous behaviour at least counted it. The presence field now carries the pin (5 recovered; referenceConstraints 579 -> 584). Latent correctness: - `optionalLiteral` hardcoded `maybeAttrString`/String instead of deriving the type from its wrapped accessor, so an `optionalLiteral(attrInt, …, 429)` pin would be mis-typed — and `field_type` is exactly what tells a consumer how to read `literalValue` back. - A request-derived output field dropped its `path` after using the tail as `wire_name`, making it indistinguishable from a field read off the response. - `find_accessor` recursed through `deref_ident` with no depth budget, so mutually referential minified bindings would blow the stack — a crash of the generator rather than a field that isn't recovered. - A merged action branch only weakened fields it contained, leaving a field absent from that branch wrongly required. - A hoisted content receiver (`var body = child.child("body")`) yielded an empty wire name; the receiver is now dereferenced through the scope. - Returns nested in `if`/`else` were skipped, exposing only the last shape of a branching arm. Nested `switch`es are still not descended into — those are a different dispatch level, not branches of this arm. - A constant-table local bound twice to different objects now resolves to nothing instead of last-one-wins, so a reused minified name cannot mint wrong wire tags. Diagnostics: - The enum drop counter was memoized, so it counted distinct enums rather than occurrences while every other reason here is per-occurrence (10 -> 65), and an enum argument that wasn't an `o("Mod").NAME` reference at all was dropped with no note. Tests: - The round-trip guard modelled only the root, so it skipped every `sourcePath` and nested-child pin — including `matched="true"` on `` and `type="admin"` on ``, i.e. it would have accepted the very malformed promote response its own header describes. It now models the stanza as a path-addressed tree: 175 nested pins exercised where there were none, plus a regression test that a wrong value on a nested node is caught. Optional containers are deliberately not descended into — WA models mutually exclusive alternatives as several optional same-node mixins (a newsletter `` pins `interaction_type` to both "question" and "question_reshare"), so descending into all of them would build a self-contradicting node. - `checked > 0` stayed green on a layer that had degraded to tag-only; the echo/pin coverage is floored too. - `handler_without_a_const_keyed_switch_has_no_actions` was vacuous: the fixture dispatched only `w:gp2`, so the loop body never ran. It now dispatches a second, action-less type, and asserts the fixture does so. Rejected: scoping action discovery to the notification's `handler_function`. The switch is not in the handler — `handleGroupNotification` builds a `WADeprecatedWapParser` around a sibling local and the child-tag switch lives there, so scoping to the handler body finds nothing and loses all 45 `w:gp2` arms. The underlying risk (a module hosting two handlers with two switches) is real but latent; a module serving more than one notification type is now skipped instead, so the failure is a visible omission rather than a union attributed to the wrong type. Determinism holds, the schemas validate, and the reference Rust codegen is still byte-identical. --- crates/wa-notif/src/actions.rs | 87 ++++++- crates/wa-notif/src/lib.rs | 29 ++- crates/wa-notif/src/tests.rs | 26 +- crates/wa-scan/src/response_index.rs | 82 ++++--- crates/wa-scan/src/response_smax.rs | 76 +++++- crates/wa-scan/tests/iq_roundtrip.rs | 347 +++++++++++++++++++-------- crates/whatspec/src/main.rs | 9 +- generated/iq/index.json | 313 ++++++++++++++++++++++-- generated/manifest.json | 16 +- generated/notif/index.json | 3 +- 10 files changed, 802 insertions(+), 186 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 5f18a77..49c2964 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -102,7 +102,7 @@ fn const_string_map(slice: &str, export: &str) -> Option { if let Some(direct) = collector.exports.get(export) { return match direct { Export::Inline(map) => Some(map.clone()), - Export::Local(name) => collector.locals.get(name).cloned(), + Export::Local(name) => collector.locals.get(name).cloned().flatten(), }; } None @@ -114,7 +114,13 @@ enum Export { } struct ConstCollector { - locals: HashMap, + /// Local name → its all-string object, or `None` when the same minified name is + /// bound to **different** objects in the module. The minifier reuses short + /// identifiers aggressively across nested scopes, so a module-wide "last one wins" + /// map could resolve an export to an unrelated nested table and mint wrong wire tags + /// or `actionType` values. Ambiguity therefore resolves to nothing, which shows up + /// as a missing action rather than a silently wrong one. + locals: HashMap>, exports: HashMap, } @@ -123,7 +129,17 @@ impl<'a> Visit<'a> for ConstCollector { if let Some(name) = d.id.get_identifier_name() && let Some(map) = d.init.as_ref().and_then(string_const_object) { - self.locals.insert(name.to_string(), map); + match self.locals.entry(name.to_string()) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(Some(map)); + } + std::collections::hash_map::Entry::Occupied(mut e) => { + // A second, different binding of the same name makes it ambiguous. + if e.get().as_ref() != Some(&map) { + e.insert(None); + } + } + } } walk::walk_variable_declarator(self, d); } @@ -313,6 +329,14 @@ fn empty_action(wire_tag: String) -> NotifActionDef { /// is present, and required only if BOTH read it unconditionally. A constant only one /// branch stamps is dropped — it is not a property of the action, only of that branch. fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { + // A field the incoming branch does not read at all is, by definition, absent from + // one legal shape of this action — so it cannot stay required just because the + // first branch read it unconditionally. Weaken those before folding the rest in. + for existing in into.fields.iter_mut() { + if !from.fields.iter().any(|f| f.name == existing.name) { + existing.required = false; + } + } for f in from.fields { match into.fields.iter_mut().find(|x| x.name == f.name) { Some(existing) => existing.required &= f.required, @@ -339,14 +363,36 @@ fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec<&'b Express other => other, }; let mut out = Vec::new(); + collect_returns(stmts, &mut out); + out +} + +/// Every `return` reachable in `stmts`, descending through control flow (`if`/`else`, +/// blocks, `try`) but never into a nested function or a nested `switch` — an arm or helper that +/// writes `if (cond) return {actionType: A, …}; return {actionType: B, …}` exposes both +/// shapes, where a direct-children-only scan would silently keep just the last. +fn collect_returns<'b, 'a>(stmts: &'b [Statement<'a>], out: &mut Vec<&'b Expression<'a>>) { for s in stmts { - if let Statement::ReturnStatement(r) = s - && let Some(arg) = r.argument.as_ref() - { - collect_branches(arg, &mut out); + match s { + Statement::ReturnStatement(r) => { + if let Some(arg) = r.argument.as_ref() { + collect_branches(arg, out); + } + } + Statement::BlockStatement(b) => collect_returns(&b.body, out), + Statement::IfStatement(i) => { + collect_returns(std::slice::from_ref(&i.consequent), out); + if let Some(alt) = &i.alternate { + collect_returns(std::slice::from_ref(alt), out); + } + } + Statement::TryStatement(t) => collect_returns(&t.block.body, out), + // Deliberately NOT descending into a nested `switch`: that is a different + // dispatch level, and its arms are other actions' shapes, not branches of + // this one. + _ => {} } } - out } fn collect_branches<'b, 'a>(e: &'b Expression<'a>, out: &mut Vec<&'b Expression<'a>>) { @@ -660,6 +706,24 @@ fn find_accessor<'b, 'a>( e: &'b Expression<'a>, scope: &Scope<'b, 'a>, ) -> Option<(String, String, bool)> { + find_accessor_at(e, scope, 0) +} + +/// How deep the wrapper-call descent goes. Bounded like every other descent in this +/// module: mutually referential minified bindings (`var a = g(b), b = g(a)` — legal JS, +/// and the minifier reuses short names aggressively) would otherwise cycle through +/// `deref_ident` and blow the stack, turning a bundle shape into a hard crash of the +/// generator instead of a field that simply isn't recovered. +const MAX_ACCESSOR_DEPTH: u8 = 8; + +fn find_accessor_at<'b, 'a>( + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + depth: u8, +) -> Option<(String, String, bool)> { + if depth > MAX_ACCESSOR_DEPTH { + return None; + } let call = as_call(deref_ident(e, scope))?; if let Some(method) = callee_method(call) { let arg0 = call.arguments.first().and_then(arg_expr); @@ -675,7 +739,12 @@ fn find_accessor<'b, 'a>( method, wap::CONTENT_STRING | wap::CONTENT_INT | wap::CONTENT_BYTES ) { + // The receiver is often hoisted (`var body = child.child("body"); … body + // .contentString()`), so it must be dereferenced through the scope first — + // otherwise the wire name comes out empty and a consumer cannot tell content + // read from a nested child apart from content read on the action node. let tag = wa_oxc::callee_object(call) + .map(|obj| deref_ident(obj, scope)) .and_then(as_call) .and_then(|inner| inner.arguments.first().and_then(arg_expr)) .and_then(as_string_lit) @@ -687,7 +756,7 @@ fn find_accessor<'b, 'a>( call.arguments .iter() .filter_map(arg_expr) - .find_map(|a| find_accessor(a, scope)) + .find_map(|a| find_accessor_at(a, scope, depth + 1)) } /// Whether `method` is a wap accessor that reads a named wire attribute. Keyed on the diff --git a/crates/wa-notif/src/lib.rs b/crates/wa-notif/src/lib.rs index 952a484..1f57969 100644 --- a/crates/wa-notif/src/lib.rs +++ b/crates/wa-notif/src/lib.rs @@ -130,13 +130,32 @@ pub fn extract_notif_from_modules( // carries a whole second union inside it (`w:gp2`'s 40+ group actions). Resolved // against the full module index, since both the case labels and the `actionType` // values are `Module.CONST.MEMBER` references defined elsewhere. + // The union is located per MODULE, not per handler function: the switch does not + // live in the exported handler at all — `handleGroupNotification` builds a + // `WADeprecatedWapParser` around a sibling local, and the child-tag switch is inside + // that local. Scoping discovery to the handler's own body would therefore find + // nothing and lose the whole `w:gp2` union. + // + // The cost of module scope is that a module hosting two handlers with two different + // child-tag switches would hand the larger union to both of its notification types. + // No module serves more than one type today, so rather than guess at which switch + // belongs to which handler, a shared module is left without actions — an omission a + // consumer can see, instead of an action union silently attributed to the wrong type. + let mut types_per_module: HashMap = HashMap::new(); + for n in &dispatch.notifications { + if let Some(m) = n.handler_module.clone() { + *types_per_module.entry(m).or_default() += 1; + } + } let consts = actions::ConstResolver::new(&slice_by_name); for n in &mut dispatch.notifications { - let Some(slice) = n - .handler_module - .as_deref() - .and_then(|m| slice_by_name.get(m)) - else { + let Some(module) = n.handler_module.as_deref() else { + continue; + }; + if types_per_module.get(module).copied().unwrap_or(0) > 1 { + continue; + } + let Some(slice) = slice_by_name.get(module) else { continue; }; if let Some(found) = actions::extract_actions(slice, &consts) { diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 9921783..6ba14aa 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -570,17 +570,26 @@ __d("WASmaxInCoexistenceOffboardingNotificationRequest",["WAResultOrError","WASm /// Reproduces every shape the real one uses — a many-to-one normalisation, a rebound /// field name, a constant-only arm, a repeated sub-element, and a two-outcome ternary. const GROUP_ACTIONS_BUNDLE: &str = r#" -__d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g,r,d,o,e,i,l){ +__d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification","WAWebHandleDeviceNotification"],function(g,r,d,o,e,i,l){ l.handle = function(){ return (function*(e,t){ var n = e.attrs; switch (e.tag) { case "notification": switch (n.type) { case "w:gp2": return yield r("WAWebHandleGroupNotification")(e); + case "devices": return yield o("WAWebHandleDeviceNotification").handleDevicesNotification(e); } } }); }; }, 1); +__d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var p = new (n("WADeprecatedWapParser"))("incomingDevicesNotification", function(e){ + e.assertTag("notification"); e.assertAttr("type","devices"); + return { id: e.attrString("id") }; + }); + function h(e){ return p.parse(e); } + l.handleDevicesNotification = h; +}), 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description"}); l.GROUP_NOTIFICATION_TAG=e; @@ -731,8 +740,21 @@ fn group_action_arms_recover_fields_children_and_branches() { #[test] fn handler_without_a_const_keyed_switch_has_no_actions() { - // Most handlers forward straight to a parser; they must not sprout a phantom union. + // Most handlers forward straight to a `WADeprecatedWapParser` and carry no action + // union; they must not sprout a phantom one. The fixture dispatches `devices` + // alongside `w:gp2` precisely so this assertion actually runs — with only `w:gp2` in + // the bundle the loop body would never execute and the property would go untested. let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let others: Vec<&str> = ir + .notifications + .iter() + .filter(|n| n.notif_type != "w:gp2") + .map(|n| n.notif_type.as_str()) + .collect(); + assert!( + others.contains(&"devices"), + "fixture must dispatch a second, action-less type or this test is vacuous" + ); for n in &ir.notifications { if n.notif_type != "w:gp2" { assert!(n.actions.is_empty(), "{} grew actions", n.notif_type); diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index bb09dd8..a41870d 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -14,7 +14,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use wa_ir::{ ErrorClass, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, ResponseVariantKind, - UnionVariant, }; use crate::response_smax::{Resolved, Resolver, analyze_module_exports, scan_cascade_variants}; @@ -136,15 +135,23 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd let assertions = resolver.assertions(&module, &func); // The per-RPC error vocabulary: which `` pairs THIS RPC's // error arm accepts. It is a closed set and it differs per RPC, so it can - // only be read off the arm's own error disjunction. - let vocab = error_vocabulary(&fields); + // only be read off the arm's own error arm. + // + // Gated on the variant kind, and not merely used to gate `error_class`: a + // SUCCESS payload can legitimately nest a union whose variant pins a `code` + // or `text` field for unrelated reasons (three PreKeys success variants do), + // and reporting that as an accepted error vocabulary contradicts the + // per-error-arm meaning documented on `ResponseVariant`. + let vocab = if kind == ResponseVariantKind::Error { + error_vocabulary(&fields) + } else { + ErrorVocabulary::default() + }; variants.push(ResponseVariant { tag, module_name: module, kind, - error_class: (kind == ResponseVariantKind::Error) - .then(|| vocab.class()) - .flatten(), + error_class: vocab.class(), error_codes: vocab.codes, error_texts: vocab.texts, error_code_min: vocab.code_min, @@ -236,15 +243,21 @@ impl ErrorVocabulary { } } -/// Collect the `` vocabulary from a variant's resolved fields. +/// Collect the `` vocabulary from an error variant's resolved fields. /// -/// An error variant's payload is an `errorXxxErrors` union field whose alternatives are -/// the per-namespace error mixins; each mixin pins its own `text`/`code` +/// The usual shape is an `errorXxxErrors` union field whose alternatives are the +/// per-namespace error mixins, each pinning its own `text`/`code` /// (`literal(attrString, e, "text", "rate-overlimit")` + `literal(attrInt, e, "code", /// 429)`), except the fallback arms, which range-check the code instead -/// (`attrIntRange(e, "code", 500, 599)`). Both forms are already carried on the fields -/// (as `literal_value` / `int_min`+`int_max`), so this reads them back rather than -/// re-parsing the bundle. +/// (`attrIntRange(e, "code", 500, 599)`). +/// +/// But a good number of RPCs skip the disjunction and parse a single `` child +/// directly — `SetResponseConflict` pins `409`/`conflict` as plain nested fields, and +/// `GetAccessTokenAndSessionCookiesResponseTooManyAttempts` pins `431` the same way. So +/// the `code`/`text` reading is applied to EVERY traversed field, not only to fields +/// inside a `UnionVariant`; restricting it to unions silently emptied the vocabulary of +/// 29 error variants. Both forms are already carried on the fields (as `literal_value` / +/// `int_min`+`int_max`), so this reads them back rather than re-parsing the bundle. fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut v = ErrorVocabulary::default(); collect_error_vocabulary(fields, &mut v); @@ -257,42 +270,35 @@ fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { fn collect_error_vocabulary(fields: &[ParsedField], out: &mut ErrorVocabulary) { for f in fields { - if let Some(variants) = &f.union_variants { - for uv in variants { - collect_variant_vocabulary(uv, out); - } - } + read_error_pin(f, out); if let Some(children) = &f.children { collect_error_vocabulary(children, out); } + for uv in f.union_variants.iter().flatten() { + collect_error_vocabulary(&uv.fields, out); + } } } -fn collect_variant_vocabulary(uv: &UnionVariant, out: &mut ErrorVocabulary) { - for f in &uv.fields { - match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { - ("code", Some(lit)) => { - if let Ok(code) = lit.parse::() { - out.codes.push(code); - } - } - ("text", Some(lit)) => out.texts.push(lit.clone()), - // A fallback arm accepts any text within a code RANGE (400–499 / 500–599), - // which no enumeration of exact codes can express. - ("code", None) => { - if let (Some(min), Some(max)) = (f.int_min, f.int_max) { - out.code_min = Some(out.code_min.map_or(min, |cur: i64| cur.min(min))); - out.code_max = Some(out.code_max.map_or(max, |cur: i64| cur.max(max))); - } +/// Read one field's contribution to the vocabulary, if it is the `` `code` or +/// `text`. +fn read_error_pin(f: &ParsedField, out: &mut ErrorVocabulary) { + match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { + ("code", Some(lit)) => { + if let Ok(code) = lit.parse::() { + out.codes.push(code); } - _ => {} } - // A variant that nests a further disjunction (union-of-unions). - if let Some(nested) = &f.union_variants { - for inner in nested { - collect_variant_vocabulary(inner, out); + ("text", Some(lit)) => out.texts.push(lit.clone()), + // A fallback arm accepts any text within a code RANGE (400–499 / 500–599), + // which no enumeration of exact codes can express. + ("code", None) => { + if let (Some(min), Some(max)) = (f.int_min, f.int_max) { + out.code_min = Some(out.code_min.map_or(min, |cur: i64| cur.min(min))); + out.code_max = Some(out.code_max.map_or(max, |cur: i64| cur.max(max))); } } + _ => {} } } diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index ff6179c..fcb06f0 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -119,7 +119,14 @@ impl<'a> Resolver<'a> { /// stanza-attr enum is a wire-token enum). fn resolve_enum(&self, module: &str, name: &str) -> Option { let key = (module.to_string(), name.to_string()); + // Count per OCCURRENCE, not per distinct enum: `dropsByReason` measures how much + // constraint data was lost, and every other reason here is per-occurrence. A + // memoized miss must therefore still be counted, so N fields validating against + // the same unresolvable enum report N losses rather than one. if let Some(hit) = self.enum_cache.borrow().get(&key) { + if hit.is_none() { + self.drop_note("response enum argument not structurally resolvable"); + } return hit.clone(); } let resolved = self @@ -664,11 +671,27 @@ fn classify_call( if literal_value.is_none() && reference_path.is_none() { resolver.drop_note("optionalLiteral attr value not statically resolvable"); } + // The wrapped accessor decides the type, exactly as in the `literal` arm. + // `field_type` is what tells a consumer how to read `literalValue` back, so + // hardcoding `String` would actively mis-type an + // `optionalLiteral(attrInt, node, "code", 429)` pin. Falls back to a string + // when the accessor isn't resolvable (the overwhelmingly common `attrString`). + let (method, field_type, byte_length) = args + .first() + .and_then(arg_expr) + .and_then(inner_accessor_name) + .and_then(normalize_accessor) + .map(|(m, ft, bl)| (optional_variant(&m), ft, bl)) + .unwrap_or(( + wap::MAYBE_ATTR_STRING.to_string(), + ParsedFieldType::String, + None, + )); Binding::Field { - method: wap::MAYBE_ATTR_STRING.to_string(), - field_type: ParsedFieldType::String, + method, + field_type, required: false, - byte_length: None, + byte_length, byte_range: None, int_range: None, wire_name, @@ -880,10 +903,14 @@ fn enum_arg_ref( // is an identifier / `X.value`, the attr a string, and — in the `optional(ACC, …)` // form — the leading accessor ref is itself `o("WASmaxParseUtils").attrStringEnum`, // excluded by requiring a non-`WASmaxParse*` owner module). - args.iter() - .filter_map(arg_expr) - .find_map(module_member_ref) - .and_then(|(module, name)| resolver.resolve_enum(&module, &name)) + let Some((module, name)) = args.iter().filter_map(arg_expr).find_map(module_member_ref) else { + // An inline enum object, a local alias, or a `WASmaxParse*`-owned reference: the + // accessor validates against SOMETHING we could not name. That is the exact + // "a constraint existed and we lost it" case the counter exists for. + resolver.drop_note("response enum argument not structurally resolvable"); + return None; + }; + resolver.resolve_enum(&module, &name) } /// `o("Mod").NAME` (a member reference, not a call) → `(Mod, NAME)`, excluding the @@ -1368,12 +1395,22 @@ fn collect_object_fields( // A boolean presence flag: `{hasX: V.success}` or `{hasX: V.value != null}` // — the key records whether a sub-node/attr was present, not its value. The // wire name (when known) is the underlying accessor's attr. + // + // The *pin* underneath survives too. The blocklist shapes bind + // `optionalLiteral(attrString, list, "c_dhash", ref.item.dhash)` and then expose + // only `{hasListCDhash: m.value != null}`: the attribute is optional, but if it + // is present the parser requires it to equal the request's ``. + // Dropping the pin here would lose that rule entirely — the flag would say + // "c_dhash may appear" and nothing would say what it must contain. if let Some(flag_var) = bool_flag_var(&p.value) { + let underlying = bindings.get(flag_var); out.push(ParsedField { name: key.to_string(), field_type: ParsedFieldType::Bool, - wire_name: bindings.get(flag_var).and_then(binding_wire_name), + wire_name: underlying.and_then(binding_wire_name), required: true, + literal_value: underlying.and_then(binding_literal_value), + reference_path: underlying.and_then(binding_reference_path), ..Default::default() }); continue; @@ -1444,6 +1481,10 @@ fn collect_object_fields( wire_name: path.last().cloned(), field_type: *field_type, required: *required && !optional, + // The value IS the request's. Keeping the path makes that machine- + // visible; without it the field is indistinguishable from one read + // off the response node. + reference_path: Some(path.clone()), ..Default::default() }); } @@ -1694,6 +1735,25 @@ fn is_nullish(e: &Expression) -> bool { fn binding_wire_name(b: &Binding) -> Option { match b { Binding::Field { wire_name, .. } => wire_name.clone(), + Binding::Reference { path, .. } => path.last().cloned(), + _ => None, + } +} + +/// The constant a binding pins its value to, when it is a `literal`/`optionalLiteral`. +fn binding_literal_value(b: &Binding) -> Option { + match b { + Binding::Field { literal_value, .. } => literal_value.clone(), + _ => None, + } +} + +/// The request path a binding echoes, whether it is the echo itself +/// (`attrStringFromReference`) or an `optionalLiteral` pinned to one. +fn binding_reference_path(b: &Binding) -> Option> { + match b { + Binding::Field { reference_path, .. } => reference_path.clone(), + Binding::Reference { path, .. } => Some(path.clone()), _ => None, } } diff --git a/crates/wa-scan/tests/iq_roundtrip.rs b/crates/wa-scan/tests/iq_roundtrip.rs index cb05f54..5dc4024 100644 --- a/crates/wa-scan/tests/iq_roundtrip.rs +++ b/crates/wa-scan/tests/iq_roundtrip.rs @@ -7,10 +7,15 @@ //! addressed to `g.us` with `from="s.whatsapp.net"` and be unparseable. //! //! So this test closes the loop the way a consumer would: for every `` success -//! variant, build a stanza from **the IR alone** — nothing but the recorded assertions -//! and pinned field values — and check the recorded assertions accept it. A constraint -//! that the IR fails to carry shows up here as an emitted stanza that the same IR then -//! rejects. +//! shape, build a stanza from **the IR alone** — nothing but the recorded assertions and +//! pinned field values — and check the recorded constraints accept it. A constraint the +//! IR fails to carry shows up here as an emitted stanza that the same IR then rejects. +//! +//! The stanza is modelled as a **tree**, addressed by the path from the root, because +//! the constraints that motivated the change do not live on the root: `matched="true"` +//! sits on the `` child (a `sourcePath`) and `type="admin"` on `` (a +//! nested `child` field). A root-only guard would accept exactly the malformed promote +//! response this file exists to reject. //! //! Both halves are deliberately naive: the emitter only knows how to satisfy //! constraints, and the checker only knows how to verify them. Neither shares code with @@ -21,112 +26,180 @@ use std::path::Path; use wa_ir::{AssertionKind, IqIr, ParsedField, ResponseAssertion, ResponseVariantKind}; -/// A minimal stanza: a tag, attributes, and text content. Everything the recorded -/// assertions can talk about. +/// A stanza under construction: the root tag, its text content, and the attributes of +/// every node reachable in it, keyed by the path from the root (`[]` is the root itself, +/// `["list"]` its `` child, `["participant"]` a `` child, …). #[derive(Debug, Default)] -struct Node { +struct Stanza { tag: String, - attrs: BTreeMap, content: Option, + nodes: BTreeMap, BTreeMap>, +} + +impl Stanza { + fn attr(&self, path: &[String], name: &str) -> Option<&String> { + self.nodes.get(path).and_then(|n| n.get(name)) + } + fn set(&mut self, path: Vec, name: String, value: String) { + self.nodes + .entry(path) + .or_default() + .entry(name) + .or_insert(value); + } } /// The request a response is answering, as far as the echo rules care: the attribute -/// values a `reference` assertion can point at. Keyed by the joined `referencePath`, so -/// a multi-hop path (`["account","action"]`) is addressable too. +/// values a `reference` constraint can point at. Keyed by the joined path, so a +/// multi-hop one (`["account","action"]`) is addressable too. fn sample_request() -> BTreeMap { // A group-addressed request — the case that broke: `to` is NOT `s.whatsapp.net`, so // an emitter that hardcodes the server JID fails the `from` echo. BTreeMap::from([ ("id".to_string(), "1234.5678-9".to_string()), ("to".to_string(), "120363000000000000@g.us".to_string()), + ("type".to_string(), "set".to_string()), ("account/action".to_string(), "sync".to_string()), + ("item/dhash".to_string(), "2:abcdef==".to_string()), + ( + "participant".to_string(), + "5511999999999@s.whatsapp.net".to_string(), + ), + ("category".to_string(), "block".to_string()), ]) } -/// Build the response node a variant describes, using only the IR. -fn emit(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> Node { +/// The value a field is pinned to, if any: a constant, or the request value it echoes. +/// `Err` marks a pin whose request path the sample doesn't model — not an IR failure, +/// but an unrecognised shape that must be reported rather than pass silently. +fn pinned_value( + f: &ParsedField, + request: &BTreeMap, +) -> Option> { + if let Some(v) = &f.literal_value { + return Some(Ok(v.clone())); + } + let path = f.reference_path.as_ref()?; + Some( + request + .get(&path.join("/")) + .cloned() + .ok_or_else(|| format!("unmodelled request path {path:?}")), + ) +} + +/// Walk a field tree, invoking `visit` with each pinned field and the path of the node +/// it is read from. +/// +/// Three shapes decide the path: `sourcePath` prepends wrapper tags to descend, a +/// `child` field descends into its own `tag`, and a `sameNode` mixin reads off the +/// parent without descending at all. +/// +/// Only a **required** container is descended into. An optional one is a branch the +/// response need not take, and WA models mutually exclusive alternatives as several +/// optional same-node mixins rather than as one union: a newsletter `` +/// carries an `InteractionTypeQuestion` mixin pinning `interaction_type="question"` and +/// an `InteractionTypeQuestionReshare` mixin pinning `"question_reshare"` on the very +/// same attribute. Both pins are correct; only one applies at a time. Descending into +/// both would build a self-contradicting node and report the IR as broken when it is +/// the walk that is wrong. Unions are skipped for the same reason unless single-armed. +fn walk_pinned( + fields: &[ParsedField], + base: &[String], + visit: &mut impl FnMut(&ParsedField, Vec), +) { + for f in fields { + let mut path = base.to_vec(); + if !f.same_node { + path.extend(f.source_path.iter().flatten().cloned()); + if let Some(tag) = &f.tag { + path.push(tag.clone()); + } + } + if f.literal_value.is_some() || f.reference_path.is_some() { + visit(f, path.clone()); + } + if !f.required { + continue; // a branch the response need not take — see above + } + if let Some(children) = &f.children { + walk_pinned(children, &path, visit); + } + if let Some([only]) = f.union_variants.as_deref() { + walk_pinned(&only.fields, &path, visit); + } + } +} + +/// Build the response a shape describes, using only the IR. +fn emit(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> Stanza { let request = sample_request(); - let mut node = Node::default(); + let mut s = Stanza::default(); + let root: Vec = Vec::new(); for a in assertions { match a.kind { - AssertionKind::Tag => node.tag = a.name.clone().unwrap_or_default(), + AssertionKind::Tag => s.tag = a.name.clone().unwrap_or_default(), AssertionKind::Attr => { if let (Some(name), Some(value)) = (&a.name, &a.value) { - node.attrs.insert(name.clone(), value.clone()); + s.set(root.clone(), name.clone(), value.clone()); } } - AssertionKind::Content => node.content = a.value.clone(), + AssertionKind::Content => s.content = a.value.clone(), // The whole point of the `reference` kind: the emitter reads the expected // value out of the REQUEST rather than inventing one. AssertionKind::Reference => { if let (Some(name), Some(path)) = (&a.name, &a.reference_path) && let Some(value) = request.get(&path.join("/")) { - node.attrs.insert(name.clone(), value.clone()); + s.set(root.clone(), name.clone(), value.clone()); } } AssertionKind::FromServer => {} } } - // Pinned field values (`type="admin"`, `matched="true"`) are constraints too: a - // required one must be emitted, and an optional one must not be contradicted. A - // field-level `referencePath` is the same rule with the value taken from the - // request (the optional twin of a `reference` assertion). - for f in fields { - if f.source_path.is_some() || f.same_node { - continue; // not read off this node + // A required pin must be emitted; an optional one may be omitted (and this emitter + // omits it, which the checker must accept). + walk_pinned(fields, &root, &mut |f, path| { + if !f.required { + return; } - let wire = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); - if let Some(value) = pinned_value(f, &request) - && f.required - { - node.attrs.entry(wire).or_insert(value); + if let Some(Ok(value)) = pinned_value(f, &request) { + let wire = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); + s.set(path, wire, value); } - } - node + }); + s } -/// The value a field is pinned to, if any: a constant, or the request value it echoes. -fn pinned_value(f: &ParsedField, request: &BTreeMap) -> Option { - if let Some(v) = &f.literal_value { - return Some(v.clone()); - } - let path = f.reference_path.as_ref()?; - request.get(&path.join("/")).cloned() -} - -/// Check the node against the same assertions, reporting each unsatisfied one. -fn violations( - node: &Node, - assertions: &[ResponseAssertion], - fields: &[ParsedField], -) -> Vec { +/// Check the stanza against the same constraints, reporting each unsatisfied one. +fn violations(s: &Stanza, assertions: &[ResponseAssertion], fields: &[ParsedField]) -> Vec { let request = sample_request(); + let root: Vec = Vec::new(); let mut out = Vec::new(); for a in assertions { match a.kind { AssertionKind::Tag => { if let Some(tag) = &a.name - && &node.tag != tag + && &s.tag != tag { - out.push(format!("tag: expected <{tag}>, got <{}>", node.tag)); + out.push(format!("tag: expected <{tag}>, got <{}>", s.tag)); } } AssertionKind::Attr => { if let (Some(name), Some(value)) = (&a.name, &a.value) - && node.attrs.get(name) != Some(value) + && s.attr(&root, name) != Some(value) { out.push(format!( "attr {name}: expected {value:?}, got {:?}", - node.attrs.get(name) + s.attr(&root, name) )); } } AssertionKind::Content => { - if node.content.as_ref() != a.value.as_ref() { + if s.content.as_ref() != a.value.as_ref() { out.push(format!( "content: expected {:?}, got {:?}", - a.value, node.content + a.value, s.content )); } } @@ -137,57 +210,58 @@ fn violations( continue; }; let Some(expected) = request.get(&path.join("/")) else { - // The path names a request field the sample doesn't model. Not a - // failure of the IR — but flag an unrecognised shape rather than - // passing silently. out.push(format!( "reference {name}: unmodelled request path {path:?}" )); continue; }; - if node.attrs.get(name) != Some(expected) { + if s.attr(&root, name) != Some(expected) { out.push(format!( "reference {name}: expected the request's {path:?} ({expected:?}), got {:?}", - node.attrs.get(name) + s.attr(&root, name) )); } } AssertionKind::FromServer => {} } } - for f in fields { - if f.source_path.is_some() || f.same_node { - continue; // not read off this node - } - // A field pinned to a request path the sample doesn't model can't be checked, - // but an unrecognised shape must still be visible rather than pass silently. - if f.literal_value.is_none() - && let Some(path) = &f.reference_path - && !request.contains_key(&path.join("/")) - { - out.push(format!( - "field {}: unmodelled request path {path:?}", - f.name - )); - continue; - } - let Some(value) = pinned_value(f, &request) else { - continue; - }; + walk_pinned(fields, &root, &mut |f, path| { let wire = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); - match (node.attrs.get(&wire), f.required) { - // A required pin must be present and exact. - (None, true) => out.push(format!("pinned {wire}: required {value:?} not emitted")), - (Some(got), _) if *got != value => out.push(format!( - "pinned {wire}: expected {value:?}, emitted {got:?}" - )), - // An optional pin may be absent; it just must not contradict. - _ => {} + let at = if path.is_empty() { + wire.clone() + } else { + format!("{}/@{wire}", path.join("/")) + }; + match pinned_value(f, &request) { + Some(Err(why)) => out.push(format!("pinned {at}: {why}")), + Some(Ok(value)) => match (s.attr(&path, &wire), f.required) { + // A required pin must be present and exact. + (None, true) => out.push(format!("pinned {at}: required {value:?} not emitted")), + (Some(got), _) if *got != value => { + out.push(format!("pinned {at}: expected {value:?}, emitted {got:?}")) + } + // An optional pin may be absent; it just must not contradict. + _ => {} + }, + None => {} } - } + }); out } +/// Whether a shape carries any of the constraint layer this guard exists to protect. +fn is_constrained(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> bool { + if assertions + .iter() + .any(|a| a.kind == AssertionKind::Reference) + { + return true; + } + let mut found = false; + walk_pinned(fields, &[], &mut |_, _| found = true); + found +} + #[test] fn every_iq_success_variant_round_trips_through_its_own_constraints() { let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../generated/iq/index.json"); @@ -206,7 +280,7 @@ fn every_iq_success_variant_round_trips_through_its_own_constraints() { let raw = std::fs::read_to_string(&path).expect("read generated/iq/index.json"); let ir: IqIr = serde_json::from_str(&raw).expect("parse the committed IQ IR"); - let mut checked = 0usize; + let (mut checked, mut constrained, mut pins) = (0usize, 0usize, 0usize); let mut failures = Vec::new(); for stanza in &ir.stanzas { // Both response shapes: a single-shape response's own assertions, and each @@ -229,6 +303,10 @@ fn every_iq_success_variant_round_trips_through_its_own_constraints() { continue; // nothing recorded to satisfy or to check } checked += 1; + if is_constrained(assertions, fields) { + constrained += 1; + } + walk_pinned(fields, &[], &mut |_, _| pins += 1); let node = emit(assertions, fields); for v in violations(&node, assertions, fields) { failures.push(format!("{} / {name}: {v}", stanza.module_name)); @@ -240,20 +318,31 @@ fn every_iq_success_variant_round_trips_through_its_own_constraints() { checked > 0, "no IQ response shape carried any assertion — the constraint layer is empty" ); + // `checked` alone would stay green on a regression that emptied every echo and pin: + // thousands of `assertTag`-only shapes would still be counted. Floor the layer this + // guard actually protects. + assert!( + constrained > 0 && pins > 0, + "{checked} shape(s) carried assertions but only {constrained} carried an echo or a \ + pinned value ({pins} pins total) — the constraint layer degraded to tag-only" + ); assert!( failures.is_empty(), "{} of {checked} IR-built response shape(s) fail their own recorded constraints:\n {}", failures.len(), failures.join("\n ") ); - eprintln!("round-tripped {checked} IQ response shape(s) from the IR alone"); + eprintln!( + "round-tripped {checked} IQ response shape(s) from the IR alone \ + ({constrained} constrained, {pins} pinned field(s) exercised)" + ); } #[test] fn a_hardcoded_from_fails_the_echo_rule() { - // The negative control: without this the round-trip could pass by vacuously - // ignoring `reference` assertions. Mirrors barback's real bug — answering a request - // addressed to `g.us` with `from="s.whatsapp.net"`. + // Negative control: without this the round-trip could pass by vacuously ignoring + // `reference` assertions. Mirrors the real bug — answering a request addressed to + // `g.us` with `from="s.whatsapp.net"`. let assertions = vec![ ResponseAssertion { kind: AssertionKind::Tag, @@ -268,11 +357,81 @@ fn a_hardcoded_from_fails_the_echo_rule() { reference_path: Some(vec!["to".into()]), }, ]; - let mut node = emit(&assertions, &[]); - assert!(violations(&node, &assertions, &[]).is_empty()); - node.attrs + let mut s = emit(&assertions, &[]); + assert!(violations(&s, &assertions, &[]).is_empty()); + s.nodes + .get_mut(&Vec::new()) + .expect("root") .insert("from".into(), "s.whatsapp.net".to_string()); - let broken = violations(&node, &assertions, &[]); + let broken = violations(&s, &assertions, &[]); assert_eq!(broken.len(), 1, "{broken:?}"); assert!(broken[0].contains("reference from"), "{broken:?}"); } + +#[test] +fn a_pin_on_a_nested_node_is_checked_where_it_lives() { + // The promote bug in the file header: `type="admin"` sits on ``, not on + // the root. A guard that only modelled the root would accept `` — so assert the nested pin is both emitted and verified at its own + // path, and that a wrong value there is caught. + let participant_type = ParsedField { + method: "attrString".into(), + name: "type".into(), + wire_name: Some("type".into()), + required: true, + literal_value: Some("admin".into()), + ..Default::default() + }; + let fields = vec![ + // `` — a pin reached through a `sourcePath` wrapper. + ParsedField { + method: "attrString".into(), + name: "listMatched".into(), + wire_name: Some("matched".into()), + required: true, + literal_value: Some("true".into()), + source_path: Some(vec!["list".into()]), + ..Default::default() + }, + // `` — a pin inside a nested `child` field. + ParsedField { + method: "child".into(), + name: "promoteParticipant".into(), + tag: Some("participant".into()), + required: true, + children: Some(vec![participant_type]), + ..Default::default() + }, + ]; + let assertions = vec![ResponseAssertion { + kind: AssertionKind::Tag, + name: Some("iq".into()), + value: None, + reference_path: None, + }]; + + let mut s = emit(&assertions, &fields); + assert!( + violations(&s, &assertions, &fields).is_empty(), + "the IR-built stanza must satisfy its own nested pins" + ); + // Both pins really landed on their own nodes, not on the root. + assert_eq!( + s.attr(&["list".to_string()], "matched"), + Some(&"true".to_string()) + ); + assert_eq!( + s.attr(&["participant".to_string()], "type"), + Some(&"admin".to_string()) + ); + assert!(s.attr(&[], "type").is_none(), "must not leak onto the root"); + + // The status-code-where-the-role-goes bug is now caught. + s.nodes + .get_mut(&vec!["participant".to_string()]) + .expect("participant node") + .insert("type".into(), "200".to_string()); + let broken = violations(&s, &assertions, &fields); + assert_eq!(broken.len(), 1, "{broken:?}"); + assert!(broken[0].contains("participant/@type"), "{broken:?}"); +} diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index 8a741f0..412e152 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -856,7 +856,14 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { let mut texts = std::collections::BTreeSet::new(); for s in &ir.stanzas { c.reference_constraints += count_references(&s.response.assertions); - walk_fields(&s.response.fields, &mut c); + // `response.fields` is a CLONE of the primary success variant's fields whenever + // `variants` is non-empty (see `response_index::build_pass`), so walking both + // would count that shape's constraints twice — and only for the 97 stanzas that + // have variants, making the inflation non-uniform and the totals simply wrong. + // Walk it directly only for the Pass-2 fallback shape, which has no variants. + if s.response.variants.is_empty() { + walk_fields(&s.response.fields, &mut c); + } for v in &s.response.variants { c.reference_constraints += count_references(&v.assertions); if v.error_class.is_some() { diff --git a/generated/iq/index.json b/generated/iq/index.json index 616e011..8999247 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -579,6 +579,13 @@ "tag": "GetExperimentConfigResponseErrorRetry", "moduleName": "WASmaxInAbPropsGetExperimentConfigResponseErrorRetry", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -1109,6 +1116,13 @@ "tag": "GetGroupExperimentConfigResponseErrorRetry", "moduleName": "WASmaxInAbPropsGetGroupExperimentConfigResponseErrorRetry", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -2504,7 +2518,11 @@ "name": "hasListCDhash", "wireName": "c_dhash", "type": "bool", - "required": true + "required": true, + "referencePath": [ + "item", + "dhash" + ] }, { "method": "attrString", @@ -2619,7 +2637,11 @@ "name": "hasListCDhash", "wireName": "c_dhash", "type": "bool", - "required": true + "required": true, + "referencePath": [ + "item", + "dhash" + ] }, { "method": "attrString", @@ -2828,7 +2850,11 @@ "name": "hasListCDhash", "wireName": "c_dhash", "type": "bool", - "required": true + "required": true, + "referencePath": [ + "item", + "dhash" + ] }, { "method": "attrString", @@ -5125,11 +5151,6 @@ "tag": "FetchKeyBundlesResponseSuccess", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseSuccess", "kind": "success", - "errorCodes": [ - 500 - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -6329,11 +6350,6 @@ "tag": "FetchMissingPreKeysResponseSuccess", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseSuccess", "kind": "success", - "errorCodes": [ - 500 - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -8143,11 +8159,6 @@ "tag": "SetResponsePreKeySuccessVnameFailure", "moduleName": "WASmaxInPreKeysSetResponsePreKeySuccessVnameFailure", "kind": "success", - "errorTexts": [ - "bad-request", - "internal-server-error", - "not-acceptable" - ], "assertions": [ { "kind": "tag", @@ -9084,6 +9095,13 @@ "tag": "GetAccessTokenAndSessionCookiesResponseTooManyAttempts", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseTooManyAttempts", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 431 + ], + "errorTexts": [ + "TOO_MANY_ATTEMPTS" + ], "assertions": [ { "kind": "tag", @@ -9153,6 +9171,13 @@ "tag": "GetAccessTokenAndSessionCookiesResponseIncorrectNonce", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseIncorrectNonce", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 432 + ], + "errorTexts": [ + "INCORRECT_NONCE" + ], "assertions": [ { "kind": "tag", @@ -10781,6 +10806,13 @@ "tag": "GetLinkedAccountsResponseForbidden", "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseForbidden", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 403 + ], + "errorTexts": [ + "forbidden" + ], "assertions": [ { "kind": "tag", @@ -10854,6 +10886,13 @@ "tag": "GetLinkedAccountsResponseError", "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -12920,6 +12959,13 @@ "tag": "GetPasskeyRequestOptionsResponseError", "moduleName": "WASmaxInMdGetPasskeyRequestOptionsResponseError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -19626,6 +19672,21 @@ "tag": "GetNewsletterMessageUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 423, + 429, + 451 + ], + "errorTexts": [ + "bad-request", + "features-disabled", + "item-not-found", + "locked", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -19967,6 +20028,13 @@ "tag": "GetNewsletterMessageUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -25633,6 +25701,23 @@ "tag": "GetNewsletterMessagesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 405, + 423, + 429, + 451 + ], + "errorTexts": [ + "bad-request", + "features-disabled", + "item-not-found", + "locked", + "not-allowed", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -26038,6 +26123,13 @@ "tag": "GetNewsletterMessagesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -26532,6 +26624,23 @@ "tag": "GetNewsletterResponsesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 405, + 423, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "locked", + "not-allowed", + "not-authorized", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -26937,6 +27046,13 @@ "tag": "GetNewsletterResponsesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -28146,6 +28262,21 @@ "tag": "GetNewsletterStatusUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 423, + 429, + 451 + ], + "errorTexts": [ + "bad-request", + "features-disabled", + "item-not-found", + "locked", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -28487,6 +28618,13 @@ "tag": "GetNewsletterStatusUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -29761,6 +29899,23 @@ "tag": "GetNewsletterStatusesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 405, + 423, + 429, + 451 + ], + "errorTexts": [ + "bad-request", + "features-disabled", + "item-not-found", + "locked", + "not-allowed", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -30166,6 +30321,13 @@ "tag": "GetNewsletterStatusesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -30537,6 +30699,19 @@ "tag": "MyAddOnsResponseClientError", "moduleName": "WASmaxInNewslettersMyAddOnsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-authorized", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -30814,6 +30989,13 @@ "tag": "MyAddOnsResponseServerError", "moduleName": "WASmaxInNewslettersMyAddOnsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -31135,6 +31317,19 @@ "tag": "StatusMyAddOnsResponseClientError", "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 401, + 404, + 429 + ], + "errorTexts": [ + "bad-request", + "item-not-found", + "not-authorized", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -31412,6 +31607,13 @@ "tag": "StatusMyAddOnsResponseServerError", "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -31582,6 +31784,21 @@ "tag": "SubscribeToLiveUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400, + 404, + 423, + 429, + 451 + ], + "errorTexts": [ + "bad-request", + "features-disabled", + "item-not-found", + "locked", + "rate-overlimit" + ], "assertions": [ { "kind": "tag", @@ -31923,6 +32140,13 @@ "tag": "SubscribeToLiveUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -32281,7 +32505,10 @@ "name": "hasCategory", "wireName": "category", "type": "bool", - "required": true + "required": true, + "referencePath": [ + "category" + ] } ] }, @@ -32916,7 +33143,11 @@ "name": "hasListCDhash", "wireName": "c_dhash", "type": "bool", - "required": true + "required": true, + "referencePath": [ + "item", + "dhash" + ] }, { "method": "attrString", @@ -35187,6 +35418,13 @@ "tag": "SignCredentialResponseErrorRetry", "moduleName": "WASmaxInPrivatestatsSignCredentialResponseErrorRetry", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -37688,6 +37926,13 @@ "tag": "GetDisclosureStageByIdsResponseClientError", "moduleName": "WASmaxInUserNoticeGetDisclosureStageByIdsResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400 + ], + "errorTexts": [ + "bad-request" + ], "assertions": [ { "kind": "tag", @@ -38067,6 +38312,13 @@ "tag": "GetDisclosuresResponseClientError", "moduleName": "WASmaxInUserNoticeGetDisclosuresResponseClientError", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 400 + ], + "errorTexts": [ + "bad-request" + ], "assertions": [ { "kind": "tag", @@ -39990,6 +40242,13 @@ "tag": "SetResponseInternalServerError", "moduleName": "WASmaxInPushConfigSetResponseInternalServerError", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 500 + ], + "errorTexts": [ + "internal-server-error" + ], "assertions": [ { "kind": "tag", @@ -40056,6 +40315,13 @@ "tag": "SetResponseConflict", "moduleName": "WASmaxInPushConfigSetResponseConflict", "kind": "error", + "errorClass": "client", + "errorCodes": [ + 409 + ], + "errorTexts": [ + "conflict" + ], "assertions": [ { "kind": "tag", @@ -94627,6 +94893,13 @@ "tag": "SendBufferResponseErrorRetry", "moduleName": "WASmaxInStatsSendBufferResponseErrorRetry", "kind": "error", + "errorClass": "server", + "errorCodes": [ + 503 + ], + "errorTexts": [ + "service-unavailable" + ], "assertions": [ { "kind": "tag", diff --git a/generated/manifest.json b/generated/manifest.json index bb615e3..9a0fbfc 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,11 +5,11 @@ "iq": { "candidateModules": 207, "constraints": { - "errorTexts": 35, - "fieldEnumRefs": 277, - "fieldLiterals": 2174, - "referenceConstraints": 579, - "typedErrorVariants": 75 + "errorTexts": 38, + "fieldEnumRefs": 146, + "fieldLiterals": 1782, + "referenceConstraints": 584, + "typedErrorVariants": 104 }, "crossModule": { "fieldsRecovered": 88, @@ -20,7 +20,7 @@ "dropsByReason": { "iq builder substring present but no AST iq call": 5, "mixin fragment (folded into requests, not a standalone stanza)": 66, - "response enum argument not structurally resolvable": 10 + "response enum argument not structurally resolvable": 65 }, "excludedFragments": 66, "stanzas": 143, @@ -59,7 +59,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "27cdff784d39eb49190bb7b2fea7160c8190892c9c978a50621b7c06ef832800" + "sha256": "1c8b2050b6b27927a03a45df37635e761a4963d2352439b6b700f92f7741503c" }, "mex": { "file": "mex/index.json", @@ -69,7 +69,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "a828c2f7ba52343e2702386b47bc07b7a93a5fa70eb213e6ea86006e4ea9a31a" + "sha256": "9763145bb76f08a005f9161cc211956f79e198388e0a4a728caf37f4a20bcb4a" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index 00cc2db..9affb81 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -2457,6 +2457,7 @@ }, { "wireTag": "unlink", + "actionType": "delete_parent_group_unlink", "children": [ { "name": "groupDatas", @@ -2656,7 +2657,7 @@ }, { "name": "subject", - "wireName": "", + "wireName": "subject", "type": "string", "required": true, "content": true From 28b8b14f41624e08993b87252f7924bdfda1c3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 17:59:01 -0300 Subject: [PATCH 04/37] review: pair the error vocabulary, and fix the losses round 2 surfaced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten further findings from two Codex passes (one landed mid-work on 5500115, one on 08c01ed). Nine applied, one already fixed, one adjudicated against a finding from the same reviewer. The flattened error vocabulary was ambiguous — the same failure class the domain exists to prevent. `errorCodes: [400, 501]` beside `errorTexts: ["bad-request", "feature-not-implemented"]` admits four combinations when the parser accepts two, so an emitter picking one value from each list can produce `400 feature-not-implemented`, which matches no branch and is unparseable. 117 variants were ambiguous this way. `ResponseVariant` now carries `errorArms: [{name, code, text, codeMin, codeMax}]` — the accepted shapes, paired — and the flat lists are derived from it as a view, so the two can never disagree. Data that was being lost: - A required echo on a DESCENDED node vanished entirely. `literal` on a node reached through `flattenedChildWithTag` records no root assertion (the assertion vocabulary is root-relative) and the initializer discarded the reference too, so a nested attribute that must mirror the request reached the IR with no constraint at all. - `locked`'s `threshold` was missing because the minifier hides the accessor in the ternary's TEST — `(n = child.maybeAttrString("threshold")) != null ? n : void 0` — and following the consequent alone finds a bare `n` that no declaration binds. - `unlink` reported a single `delete_parent_group_unlink`. Its helper branches into four different actions, and the recursive return collector added last round exposed them only to have them folded into one — keeping whichever `actionType` resolved first. A wholesale delegation now expands the helper's branches into sibling arms and runs them through the same merge-by-`actionType` grouping the switch arms use. 55 -> 58 arms. Latent: - A helper name bound twice now resolves to nothing rather than last-one-wins, so a nested function reusing a minified identifier cannot make an arm inline an unrelated function. Same rule the constant tables already use. - A switch fall-through label (`case A: case B: return {…}`) inherits the next executed body instead of being recorded as an empty action. Not present in today's bundle. Diagnostics: - The enum drop counter double-counted: the same parser source is analyzed twice (once for fields, once for assertions) and a module is reachable from several RPCs, so per-invocation counting reported one lost enum many times. CodeRabbit asked for per-occurrence and Codex for once-per-loss; both are right about the failure and the unit is the disagreement. Drops are now keyed by the constraint lost, so the number means "distinct constraints" — back to 10, and stable across analysis passes. - The notif floor counted ARMS, which cannot regress while the switch is still recognized: every case yields one arm even if its whole shape vanished. `actionShapes` (resolved action types + fields + constants + child fields, 181) is floored alongside it, as is `errorArms` (639). Adjudicated: carrying an optional echo on a `Bool` presence flag, which the previous round added to recover the blocklist `c_dhash` rule, was then flagged for conflating an always-present boolean with an optional string attribute. Both halves are right — the rule must be recorded, and `required: true` on the flag must not be read as "the attribute is required". The rule is now explicit rather than inferred: on a `Bool` field the pin constrains the underlying attribute *when present*, never the boolean, documented on both `literalValue` and `referencePath`. The round-trip guard was demonstrating the harm (it demanded an optional `c_dhash` and compared a request string with a boolean) and now honours the rule, with a regression test. Already fixed before the comment landed: gating the whole error vocabulary on `kind == Error` (that pass reviewed 5500115; 08c01ed had it). No `--allow-shrink` needed. Determinism holds, schemas validate, and the reference codegen is still byte-identical. --- crates/wa-ir/src/iq.rs | 69 +- crates/wa-notif/src/actions.rs | 149 +- crates/wa-notif/src/tests.rs | 57 +- crates/wa-scan/src/response_index.rs | 139 +- crates/wa-scan/src/response_smax.rs | 94 +- crates/wa-scan/tests/iq_roundtrip.rs | 58 +- crates/whatspec/src/main.rs | 38 +- generated/iq/index.json | 3454 +++++++++++++++++++++++++ generated/manifest.json | 10 +- generated/notif/index.json | 97 + generated/schema/incoming.schema.json | 59 +- generated/schema/iq.schema.json | 59 +- generated/schema/notif.schema.json | 59 +- generated/schema/srvreq.schema.json | 59 +- generated/schema/stanza.schema.json | 59 +- 15 files changed, 4367 insertions(+), 93 deletions(-) diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index 85a5d8d..0efc8ab 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -467,8 +467,15 @@ pub struct ParsedField { /// an **optional** one (`optionalLiteral`) is pinned only when present — the emitter /// may omit the attribute, but must never send a contradicting value. /// + /// **On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a + /// field is a *presence flag* — `{hasListCDhash: m.value != null}` — so its + /// `required: true` describes the flag (the parser always produces it), never the + /// wire attribute named by [`wire_name`], which by construction may be absent. The + /// pin constrains that attribute when present, and never the boolean itself. + /// /// [`field_type`]: ParsedField::field_type /// [`required`]: ParsedField::required + /// [`wire_name`]: ParsedField::wire_name #[serde(default, skip_serializing_if = "Option::is_none")] pub literal_value: Option, /// The field is pinned not to a constant but to **a value taken from the request** — @@ -479,7 +486,9 @@ pub struct ParsedField { /// it must hold for the variant to match at all. An **optional** one /// (`optionalLiteral(…, "c_dhash", ref.item.dhash)`) is not a guard — the attribute /// may be absent — so it lives here instead: an emitter may omit it, but if it sends - /// it, the value must be the request's. Mutually exclusive with [`literal_value`]. + /// it, the value must be the request's. Mutually exclusive with [`literal_value`], + /// and — like it — always conditional when carried on a [`ParsedFieldType::Bool`] + /// presence flag. /// /// [`literal_value`]: ParsedField::literal_value #[serde(default, skip_serializing_if = "Option::is_none")] @@ -536,6 +545,45 @@ pub enum ErrorClass { Server, } +/// One accepted `` shape of a response variant: the `code` and `text` that must +/// occur **together**. +/// +/// The flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer "what +/// values does this RPC accept?", but they cannot say which value goes with which: an +/// arm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes +/// and two texts, and nothing then rules out `400 feature-not-implemented` — a +/// combination the parser rejects. An emitter that picks one value from each list +/// therefore produces an unparseable stanza, which is the exact failure class this +/// domain exists to prevent. The pairing lives here. +/// +/// [`error_texts`]: ResponseVariant::error_texts +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ErrorArm { + /// The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the + /// error is parsed as a `…Errors` union. `None` for an RPC that parses a single + /// `` child directly, which has exactly one arm and no name for it. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The exact `code` this arm pins, when it pins one. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code: Option, + /// The exact `text` this arm pins. Absent on a fallback arm, which accepts any text + /// within [`code_min`]..=[`code_max`]. + /// + /// [`code_min`]: ErrorArm::code_min + /// [`code_max`]: ErrorArm::code_max + #[serde(default, skip_serializing_if = "Option::is_none")] + pub text: Option, + /// Inclusive lower bound of a fallback arm's accepted code range. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_min: Option, + /// Inclusive upper bound of a fallback arm's accepted code range. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_max: Option, +} + /// One alternative of a response-root discriminated union (an `WASmaxInResponse` /// variant aggregated by the `WASmaxRPC` orchestrator). #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] @@ -552,7 +600,13 @@ pub struct ResponseVariant { /// no recoverable code evidence. #[serde(default, skip_serializing_if = "Option::is_none")] pub error_class: Option, - /// The **closed set** of `` values this variant accepts, ascending. + /// The accepted `` shapes, in parser order — **which code goes with which + /// text**. This is the authoritative form; see [`ErrorArm`] for why the flattened + /// lists below cannot express it. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub error_arms: Vec, + /// The **closed set** of `` values this variant accepts, ascending — the + /// union over [`error_arms`], for the "does this RPC accept code N?" question. /// /// The vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm /// takes only `400` and `429` and **rejects `404`**, even though an @@ -560,12 +614,19 @@ pub struct ResponseVariant { /// outside this set matches no branch, so the client reports a parse failure rather /// than the error. Empty when the variant pins no exact code (see [`error_code_min`]). /// + /// An emitter must pick a **pair** from [`error_arms`], never one value from this + /// list and another from [`error_texts`]. + /// + /// [`error_arms`]: ResponseVariant::error_arms + /// [`error_texts`]: ResponseVariant::error_texts /// [`error_code_min`]: ResponseVariant::error_code_min #[serde(default, skip_serializing_if = "Vec::is_empty")] pub error_codes: Vec, - /// The `` values this variant accepts, sorted — the closed vocabulary - /// paired with [`error_codes`]. Empty when only a fallback (any-text) arm applies. + /// The `` values this variant accepts, sorted — the union over + /// [`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning + /// on [`error_codes`] about combining the two lists. /// + /// [`error_arms`]: ResponseVariant::error_arms /// [`error_codes`]: ResponseVariant::error_codes #[serde(default, skip_serializing_if = "Vec::is_empty")] pub error_texts: Vec, diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 49c2964..56f5d38 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -198,12 +198,29 @@ pub(crate) fn extract_actions( // the helpers are indexed up front and inlined on demand. let mut locals = LocalFns::default(); locals.visit_program(&ret.program); + // A minified helper name reused by a nested function would otherwise resolve to + // whichever declaration was visited last, letting an arm inline an unrelated + // function and emit wrong fields. Ambiguity resolves to nothing instead — the same + // rule the constant tables use — so the failure is a missing action, not a wrong one. + let mut by_name: HashMap> = HashMap::new(); + for (name, (a, b)) in locals.spans { + let src = handler_slice[a..b].to_string(); + match by_name.entry(name) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(Some(src)); + } + std::collections::hash_map::Entry::Occupied(mut e) => { + if e.get().as_deref() != Some(src.as_str()) { + e.insert(None); + } + } + } + } let ctx = ArmCtx { consts, - locals: locals - .spans + locals: by_name .into_iter() - .map(|(n, (a, b))| (n, handler_slice[a..b].to_string())) + .filter_map(|(n, src)| src.map(|s| (n, s))) .collect(), }; let mut finder = SwitchFinder { @@ -276,11 +293,20 @@ impl<'a> Visit<'a> for SwitchFinder<'_, 'a> { /// Read every arm of a const-keyed child-tag switch into [`NotifActionDef`]s. fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec { let mut out = Vec::new(); - for case in &switch.cases { + for (i, case) in switch.cases.iter().enumerate() { let Some(wire_tag) = case.test.as_ref().and_then(|t| ctx.consts.resolve(t)) else { continue; }; - let shapes = arm_result_shapes(&case.consequent); + // `case TAG_A: case TAG_B: return {…}` — a fall-through label has an empty body + // and runs the next non-empty one. Recording it as an empty action would lose the + // action type and every field of a legally dispatched tag. + let consequent = switch.cases[i..] + .iter() + .map(|c| &c.consequent) + .find(|c| !c.is_empty()) + .map(|c| &c[..]) + .unwrap_or(&case.consequent); + let shapes = arm_result_shapes(consequent); if shapes.is_empty() { // A recognised tag whose arm returns no shape (a bare flag set, an early // break). Still catalogued: knowing the tag is dispatched at all beats @@ -295,19 +321,20 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // So branches are grouped by `actionType`: different ones stay separate arms of // the union, identical ones merge into one (a field only some branches read is // optional in the merge). - let scope = scope_bindings(match &case.consequent[..] { + let scope = scope_bindings(match consequent { [Statement::BlockStatement(b)] => &b.body[..], other => other, }); let mut grouped: Vec = Vec::new(); for shape in shapes { - let action = read_action(wire_tag.clone(), shape, &scope, ctx, 0); - match grouped - .iter_mut() - .find(|g| g.action_type == action.action_type) - { - Some(existing) => merge_action(existing, action), - None => grouped.push(action), + for action in expand_shape(&wire_tag, shape, &scope, ctx, 0) { + match grouped + .iter_mut() + .find(|g| g.action_type == action.action_type) + { + Some(existing) => merge_action(existing, action), + None => grouped.push(action), + } } } out.extend(grouped); @@ -442,6 +469,65 @@ fn deref_ident<'b, 'a>(e: &'b Expression<'a>, scope: &Scope<'b, 'a>) -> &'b Expr cur } +/// Turn one arm result shape into the action(s) it can produce. +/// +/// A shape that *delegates wholesale* to a module-local helper (`case UNLINK: return +/// I(child)`) is not one action: the helper branches, and its branches can be different +/// actions — `I` returns `delete_parent_group_unlink`, `integrity_parent_group_unlink` +/// and the sub-group variants depending on `unlink_type` and `unlink_reason`. Folding +/// them into one definition keeps whichever `actionType` resolved first and silently +/// drops the rest, so the helper's branches are expanded into sibling shapes and run +/// through the same merge-by-`actionType` grouping the switch arms use. +/// +/// A helper called in *value position* (`participants: y(chat, child, tag)`) is a +/// different thing — it contributes fields to the enclosing action — and keeps being +/// folded in by [`fold_object`]. +fn expand_shape( + wire_tag: &str, + result: &Expression, + scope: &Scope, + ctx: &ArmCtx, + depth: u8, +) -> Vec { + if depth <= MAX_INLINE_DEPTH + && let Some(src) = local_call_source(deref_ident(result, scope), ctx) + { + let expanded = expand_helper(wire_tag, &src, ctx, depth + 1); + if !expanded.is_empty() { + return expanded; + } + } + vec![read_action(wire_tag.to_string(), result, scope, ctx, depth)] +} + +/// Parse a helper and expand each of its result branches, inside this parse's own +/// allocator (only the owned definitions escape). +fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec { + let alloc = Allocator::default(); + let wrapped = format!("({fn_src})"); + let ret = wa_oxc::parse_cjs(&alloc, &wrapped); + if ret.panicked { + return Vec::new(); + } + let Some(stmts) = ret.program.body.iter().find_map(|s| match s { + Statement::ExpressionStatement(es) => function_body_of(&es.expression), + _ => None, + }) else { + return Vec::new(); + }; + let scope = scope_bindings(stmts); + let mut out: Vec = Vec::new(); + for shape in arm_result_shapes(stmts) { + for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { + match out.iter_mut().find(|g| g.action_type == action.action_type) { + Some(existing) => merge_action(existing, action), + None => out.push(action), + } + } + } + out +} + /// Read one arm result shape into a [`NotifActionDef`]. /// /// The shape is an object literal, a `babelHelpers.extends(a, b, …)` of several, or a @@ -686,9 +772,21 @@ fn is_guarded(e: &Expression) -> bool { } /// The value branch of a `cond ? value : fallback` / `a || b` guard, else `e` itself. +/// +/// The minifier's null-coalesce idiom hides the accessor in the *test*: +/// `(x = child.maybeAttrString("threshold")) != null ? x : void 0`. The consequent is +/// then a bare `x` that no declaration binds, so following it alone loses the field +/// (`locked`'s `threshold` vanished exactly this way). When the consequent is an +/// identifier assigned inside the test, the assignment's right-hand side is the value. fn strip_guard<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { match e { - Expression::ConditionalExpression(c) => strip_guard(&c.consequent), + Expression::ConditionalExpression(c) => { + let value = strip_guard(&c.consequent); + match as_identifier(value).and_then(|name| assigned_in(&c.test, name)) { + Some(assigned) => strip_guard(assigned), + None => value, + } + } Expression::ParenthesizedExpression(p) => strip_guard(&p.expression), Expression::LogicalExpression(l) => strip_guard(&l.left), Expression::AssignmentExpression(a) => strip_guard(&a.right), @@ -696,6 +794,29 @@ fn strip_guard<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { } } +/// The right-hand side of an `name = ` assignment somewhere inside `e`. +fn assigned_in<'b, 'a>(e: &'b Expression<'a>, name: &str) -> Option<&'b Expression<'a>> { + match e { + Expression::AssignmentExpression(a) => { + let target = a.left.get_identifier_name().map(|n| n.to_string()); + if target.as_deref() == Some(name) { + Some(&a.right) + } else { + assigned_in(&a.right, name) + } + } + Expression::ParenthesizedExpression(p) => assigned_in(&p.expression, name), + Expression::BinaryExpression(b) => { + assigned_in(&b.left, name).or_else(|| assigned_in(&b.right, name)) + } + Expression::LogicalExpression(l) => { + assigned_in(&l.left, name).or_else(|| assigned_in(&l.right, name)) + } + Expression::UnaryExpression(u) => assigned_in(&u.argument, name), + _ => None, + } +} + /// The innermost wap accessor call in `e` → `(method, wireName, isContentRead)`. /// /// Descends through the wrapper calls an arm applies to a raw wire value diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 6ba14aa..d2cd691 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -587,11 +587,16 @@ __d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o, e.assertTag("notification"); e.assertAttr("type","devices"); return { id: e.attrString("id") }; }); + function I(e){ + var t=e.attrString("unlink_type"); + if(t==="parent_group") return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:e.attrString("id")}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_ADD, descId:e.attrString("id")}; + } function h(e){ return p.parse(e); } l.handleDevicesNotification = h; }), 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description"}); + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ @@ -602,6 +607,11 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name") }; }); } + function I(e){ + var t=e.attrString("unlink_type"); + if(t==="parent_group") return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:e.attrString("id")}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_ADD, descId:e.attrString("id")}; + } function h(e){ var x = e.mapChildren(function(t){ var a = t.tag(); @@ -614,8 +624,12 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:0}; - case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { + var n; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.UNLINK: + return I(t); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.REVOKE_INVITE: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.REVOKE_INVITE, participants:t.mapChildrenWithTag("participant", function(p){ return {id:p.attrUserJid("jid"), expiration:p.attrInt("expiration")}; })}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DESC: @@ -761,3 +775,40 @@ fn handler_without_a_const_keyed_switch_has_no_actions() { } } } + +#[test] +fn a_null_coalesced_field_keeps_its_wire_source() { + // The minifier hides the accessor in the ternary's TEST: + // `(n = child.maybeAttrString("threshold")) != null ? n : void 0`. Following the + // consequent alone finds a bare `n` that nothing binds, and the field vanishes — + // which is exactly how `locked`'s `threshold` was being lost. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let locked = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "locked") + .expect("locked arm"); + let threshold = locked + .fields + .iter() + .find(|f| f.name == "threshold") + .expect("threshold field"); + assert_eq!(threshold.wire_name, "threshold"); + assert!(!threshold.required, "read behind a null-coalesce guard"); +} + +#[test] +fn a_helper_returning_several_actions_yields_several_arms() { + // `case UNLINK: return I(child)` delegates wholesale to a helper that branches into + // DIFFERENT actions. Folding them into one definition keeps whichever `actionType` + // resolved first and silently drops the rest, so the branches must surface as + // sibling arms of the union — the same treatment an inline ternary gets. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let unlink: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "unlink") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!(unlink, ["desc_remove", "desc_add"]); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index a41870d..f873d16 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -13,7 +13,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use wa_ir::{ - ErrorClass, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, ResponseVariantKind, + ErrorArm, ErrorClass, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, + ResponseVariantKind, }; use crate::response_smax::{Resolved, Resolver, analyze_module_exports, scan_cascade_variants}; @@ -152,6 +153,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd module_name: module, kind, error_class: vocab.class(), + error_arms: vocab.arms, error_codes: vocab.codes, error_texts: vocab.texts, error_code_min: vocab.code_min, @@ -207,6 +209,9 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd /// disjunction. #[derive(Default)] struct ErrorVocabulary { + /// The accepted `(code, text)` shapes, in parser order — the authoritative form. + arms: Vec, + /// Flattened unions over `arms`, kept for the "does this RPC accept N?" question. codes: Vec, texts: Vec, code_min: Option, @@ -260,7 +265,16 @@ impl ErrorVocabulary { /// `int_min`+`int_max`), so this reads them back rather than re-parsing the bundle. fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut v = ErrorVocabulary::default(); - collect_error_vocabulary(fields, &mut v); + collect_error_arms(fields, &mut v.arms); + // The flat lists are a VIEW over the arms, so the two can never disagree. + for a in &v.arms { + v.codes.extend(a.code); + v.texts.extend(a.text.clone()); + if let (Some(min), Some(max)) = (a.code_min, a.code_max) { + v.code_min = Some(v.code_min.map_or(min, |cur: i64| cur.min(min))); + v.code_max = Some(v.code_max.map_or(max, |cur: i64| cur.max(max))); + } + } v.codes.sort_unstable(); v.codes.dedup(); v.texts.sort(); @@ -268,38 +282,77 @@ fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { v } -fn collect_error_vocabulary(fields: &[ParsedField], out: &mut ErrorVocabulary) { - for f in fields { - read_error_pin(f, out); - if let Some(children) = &f.children { - collect_error_vocabulary(children, out); +/// One arm per accepted `` shape, so the `(code, text)` pairing survives. +/// +/// A `…Errors` disjunction contributes one arm per alternative; an RPC that parses a +/// single `` child directly contributes exactly one unnamed arm. Flattening these +/// into two independent lists would let an emitter combine one arm's code with another's +/// text — `400 feature-not-implemented` matches no branch and is unparseable, which is +/// the whole failure class this domain exists to prevent. +fn collect_error_arms(fields: &[ParsedField], out: &mut Vec) { + let mut found_union = false; + collect_union_arms(fields, out, &mut found_union); + if !found_union { + // No disjunction: the variant parses one `` shape, so its pins are one arm. + let arm = arm_pins(fields); + if arm != ErrorArm::default() { + out.push(arm); } + } +} + +/// Walk the tree for disjunction alternatives, pushing one arm per leaf alternative. +fn collect_union_arms(fields: &[ParsedField], out: &mut Vec, found: &mut bool) { + for f in fields { for uv in f.union_variants.iter().flatten() { - collect_error_vocabulary(&uv.fields, out); + *found = true; + let before = out.len(); + let mut nested = false; + collect_union_arms(&uv.fields, out, &mut nested); + if nested { + // A union of unions: the inner alternative names are the specific ones, + // so only name the arms that have none yet. + for a in &mut out[before..] { + a.name.get_or_insert_with(|| uv.name.clone()); + } + } else { + let mut arm = arm_pins(&uv.fields); + arm.name = Some(uv.name.clone()); + out.push(arm); + } + } + if let Some(children) = &f.children { + collect_union_arms(children, out, found); } } } -/// Read one field's contribution to the vocabulary, if it is the `` `code` or -/// `text`. -fn read_error_pin(f: &ParsedField, out: &mut ErrorVocabulary) { - match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { - ("code", Some(lit)) => { - if let Ok(code) = lit.parse::() { - out.codes.push(code); +/// The `code`/`text` pins of one error shape, scanning its whole field subtree but not +/// descending into disjunction alternatives (each of those is its own arm). +fn arm_pins(fields: &[ParsedField]) -> ErrorArm { + let mut arm = ErrorArm::default(); + fn walk(fields: &[ParsedField], arm: &mut ErrorArm) { + for f in fields { + match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { + ("code", Some(lit)) => arm.code = lit.parse::().ok().or(arm.code), + ("text", Some(lit)) => arm.text = Some(lit.clone()), + // A fallback arm accepts any text within a code RANGE (400–499 / + // 500–599), which no enumeration of exact codes can express. + ("code", None) => { + if let (Some(min), Some(max)) = (f.int_min, f.int_max) { + arm.code_min = Some(min); + arm.code_max = Some(max); + } + } + _ => {} } - } - ("text", Some(lit)) => out.texts.push(lit.clone()), - // A fallback arm accepts any text within a code RANGE (400–499 / 500–599), - // which no enumeration of exact codes can express. - ("code", None) => { - if let (Some(min), Some(max)) = (f.int_min, f.int_max) { - out.code_min = Some(out.code_min.map_or(min, |cur: i64| cur.min(min))); - out.code_max = Some(out.code_max.map_or(max, |cur: i64| cur.max(max))); + if let Some(children) = &f.children { + walk(children, arm); } } - _ => {} } + walk(fields, &mut arm); + arm } /// A discriminated-union field carrying `variants`. @@ -496,6 +549,44 @@ mod tests { ); } + #[test] + fn error_arms_keep_code_and_text_paired() { + // The flattened lists cannot say which code goes with which text: an arm taking + // 400/bad-request and 429/rate-overlimit flattens to two codes and two texts, + // and nothing then rules out `400 rate-overlimit` — a combination the parser + // rejects. An emitter picking one value from each list would produce exactly the + // unparseable stanza this domain exists to prevent, so the pairing is carried. + let defs = wa_transform::extract_module_definitions(ERROR_RPC); + let idx = build_pass(&defs, ERROR_RPC); + let pr = idx.get_by_x("FooGetBar").expect("indexed by X"); + let v = |tag: &str| pr.variants.iter().find(|v| v.tag == tag).expect(tag); + + let ce = v("GetBarResponseClientError"); + assert_eq!(ce.error_arms.len(), 1); + let arm = &ce.error_arms[0]; + assert_eq!(arm.name.as_deref(), Some("IQErrorBadRequest")); + assert_eq!( + (arm.code, arm.text.as_deref()), + (Some(400), Some("bad-request")) + ); + // The flat lists stay a VIEW over the arms, so the two can never disagree. + assert_eq!(ce.error_codes, vec![400]); + assert_eq!(ce.error_texts, vec!["bad-request".to_string()]); + + // A fallback arm pins a range and NO text: it accepts any text in 500..=599, so + // a `text` of `None` there is the fact, not a gap. + let se = v("GetBarResponseServerError"); + assert_eq!(se.error_arms.len(), 1); + let fb = &se.error_arms[0]; + assert_eq!(fb.name.as_deref(), Some("IQErrorFallbackServer")); + assert_eq!(fb.code, None); + assert_eq!(fb.text, None); + assert_eq!((fb.code_min, fb.code_max), (Some(500), Some(599))); + + // A success variant carries no arms at all. + assert!(v("GetBarResponseSuccess").error_arms.is_empty()); + } + #[test] fn error_class_is_derived_from_codes_not_from_the_variant_name() { // Codes decide, so `…ResponseInternalServerError` (which reads like a server diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index fcb06f0..9970b13 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -46,6 +46,9 @@ use wa_ir::{ use wa_oxc::{arg_expr, as_call, as_identifier, as_int, as_string_lit, callee_method}; +/// Drop reason for an enum accessor whose enum could not be resolved to its variants. +const ENUM_DROP: &str = "response enum argument not structurally resolvable"; + /// A module's local parser functions, keyed by name → re-parsable source. Child /// accessors reference these by identifier (`optionalChildWithTag(n, "x", parseX)`). type LocalFns = HashMap; @@ -77,10 +80,17 @@ pub(crate) struct Resolver<'a> { /// Memoized `(module, enum)` → resolved wire enum, for the enum argument of an /// `attrStringEnum`/`contentStringEnum` accessor; see [`Resolver::resolve_enum`]. enum_cache: RefCell>>, - /// Constraints seen but **not** structurally resolvable, by reason. Surfaced under + /// Constraints seen but **not** structurally resolvable: reason → the set of + /// distinct constraints lost under it. Surfaced (as set sizes) under /// `manifest.diagnostics.iq.dropsByReason` so a consumer can tell "this field carries /// no constraint" from "a constraint was there and we failed to extract it". - drops: RefCell>, + /// + /// Keyed rather than counted because the same parser source is analyzed more than + /// once — `resolve` walks it for fields and `assertions` walks it again for + /// discriminators, and a module can be reached from several RPCs. A raw counter would + /// therefore report one lost enum several times, which says nothing about how much + /// data is actually missing. The unit is **distinct constraints**. + drops: RefCell>>, } impl<'a> Resolver<'a> { @@ -99,16 +109,21 @@ impl<'a> Resolver<'a> { /// The constraints this resolver saw but could not resolve, by reason (see /// [`Resolver::drops`]). Snapshot; safe to call after a scan. pub(crate) fn drop_counts(&self) -> BTreeMap { - self.drops.borrow().clone() + self.drops + .borrow() + .iter() + .map(|(reason, keys)| (reason.clone(), keys.len())) + .collect() } - /// Record one unresolvable constraint under `reason`. - fn drop_note(&self, reason: &str) { - *self - .drops + /// Record one unresolvable constraint under `reason`, identified by `key` so the + /// same loss seen again on a second analysis pass is not counted twice. + fn drop_note_keyed(&self, reason: &str, key: String) { + self.drops .borrow_mut() .entry(reason.to_string()) - .or_insert(0) += 1; + .or_default() + .insert(key); } /// Resolve the enum argument of an enum accessor (`o("WASmaxInFooEnums").ENUM_OFF_ON`) @@ -125,7 +140,7 @@ impl<'a> Resolver<'a> { // the same unresolvable enum report N losses rather than one. if let Some(hit) = self.enum_cache.borrow().get(&key) { if hit.is_none() { - self.drop_note("response enum argument not structurally resolvable"); + self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}")); } return hit.clone(); } @@ -152,7 +167,7 @@ impl<'a> Resolver<'a> { }) }); if resolved.is_none() { - self.drop_note("response enum argument not structurally resolvable"); + self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}")); } self.enum_cache.borrow_mut().insert(key, resolved.clone()); resolved @@ -598,9 +613,10 @@ fn classify_call( value: None, reference_path: Some(path.clone()), }), - (None, None) => { - resolver.drop_note("literal attr value not statically resolvable") - } + (None, None) => resolver.drop_note_keyed( + "literal attr value not statically resolvable", + attr.to_string(), + ), } } let inner = args @@ -619,7 +635,11 @@ fn classify_call( source_path, literal_value, enum_ref: None, - reference_path: None, + // A `literal` on a DESCENDED node records no root assertion (the + // assertion vocabulary is root-relative), so without carrying the + // echo here a nested attribute that must mirror the request would + // land in the IR with no constraint at all. + reference_path, }, None => Binding::None, } @@ -669,7 +689,10 @@ fn classify_call( .and_then(arg_expr) .and_then(|e| reference_path_of(e, bindings)); if literal_value.is_none() && reference_path.is_none() { - resolver.drop_note("optionalLiteral attr value not statically resolvable"); + resolver.drop_note_keyed( + "optionalLiteral attr value not statically resolvable", + wire_name.clone().unwrap_or_default(), + ); } // The wrapped accessor decides the type, exactly as in the `literal` arm. // `field_type` is what tells a consumer how to read `literalValue` back, so @@ -823,7 +846,10 @@ fn classify_reference( .iter() .position(|a| arg_expr(a).is_some_and(|e| string_array(e).is_some())) else { - resolver.drop_note("reference path argument not statically resolvable"); + resolver.drop_note_keyed( + "reference path argument not statically resolvable", + method.to_string(), + ); return Binding::None; }; let path = arg_expr(&args[path_idx]) @@ -840,7 +866,10 @@ fn classify_reference( .rev() .find_map(|a| arg_expr(a).and_then(as_identifier)); if node.is_none() || node != reference_param { - resolver.drop_note("reference read from a node other than the request"); + resolver.drop_note_keyed( + "reference read from a node other than the request", + format!("{method}:{}", path.join("/")), + ); return Binding::None; } // An explicit accessor (the `attrFromReference` family) decides the type; the @@ -907,7 +936,14 @@ fn enum_arg_ref( // An inline enum object, a local alias, or a `WASmaxParse*`-owned reference: the // accessor validates against SOMETHING we could not name. That is the exact // "a constraint existed and we lost it" case the counter exists for. - resolver.drop_note("response enum argument not structurally resolvable"); + resolver.drop_note_keyed( + ENUM_DROP, + args.iter() + .filter_map(arg_expr) + .find_map(as_string_lit) + .unwrap_or("") + .to_string(), + ); return None; }; resolver.resolve_enum(&module, &name) @@ -2102,6 +2138,28 @@ mod tests { ); } + #[test] + fn a_required_echo_on_a_descended_node_survives() { + // A `literal` on a node reached through `flattenedChildWithTag` records no root + // assertion — the assertion vocabulary is root-relative — so the echo has to ride + // on the field, or the nested attribute reaches the IR with no constraint at all + // and an emitter can put anything there. + let body = r#"function e(node, ref){ + var t = o("WASmaxParseUtils").flattenedChildWithTag(node, "list"); if(!t.success) return t; + var r = o("WASmaxParseReference").attrStringFromReference(ref, ["id"]); if(!r.success) return r; + var a = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, t.value, "id", r.value); + return o("WAResultOrError").makeResult({ listId: a.value }); + }"#; + let (asserts, fields) = analyze_one(body).expect("analyzed"); + assert!( + !asserts.iter().any(|a| a.kind == AssertionKind::Reference), + "the guard is on , not the root, so it must not become a root assertion" + ); + let f = fields.iter().find(|f| f.name == "listId").expect("listId"); + assert_eq!(f.source_path.as_deref(), Some(&["list".to_string()][..])); + assert_eq!(f.reference_path.as_deref(), Some(&["id".to_string()][..])); + } + #[test] fn multi_hop_reference_path_is_kept_whole() { // `attrStringFromReference(request, ["account","action"])` reads the `action` diff --git a/crates/wa-scan/tests/iq_roundtrip.rs b/crates/wa-scan/tests/iq_roundtrip.rs index 5dc4024..7ef3b65 100644 --- a/crates/wa-scan/tests/iq_roundtrip.rs +++ b/crates/wa-scan/tests/iq_roundtrip.rs @@ -160,7 +160,7 @@ fn emit(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> Stanza { // A required pin must be emitted; an optional one may be omitted (and this emitter // omits it, which the checker must accept). walk_pinned(fields, &root, &mut |f, path| { - if !f.required { + if !pin_is_required(f) { return; } if let Some(Ok(value)) = pinned_value(f, &request) { @@ -234,7 +234,7 @@ fn violations(s: &Stanza, assertions: &[ResponseAssertion], fields: &[ParsedFiel }; match pinned_value(f, &request) { Some(Err(why)) => out.push(format!("pinned {at}: {why}")), - Some(Ok(value)) => match (s.attr(&path, &wire), f.required) { + Some(Ok(value)) => match (s.attr(&path, &wire), pin_is_required(f)) { // A required pin must be present and exact. (None, true) => out.push(format!("pinned {at}: required {value:?} not emitted")), (Some(got), _) if *got != value => { @@ -249,6 +249,18 @@ fn violations(s: &Stanza, assertions: &[ResponseAssertion], fields: &[ParsedFiel out } +/// Whether the pinned attribute must be present. +/// +/// **Not** simply `f.required`. On a [`wa_ir::ParsedFieldType::Bool`] field the pin is +/// always conditional: such a field is a *presence flag* (`{hasListCDhash: m.value != +/// null}`), so `required: true` describes the flag the parser always produces, never the +/// wire attribute it reports on — which may by construction be absent. Treating the flag +/// as the attribute would have this emitter demand an optional `c_dhash` and then compare +/// a request string against a boolean. +fn pin_is_required(f: &ParsedField) -> bool { + f.required && f.field_type != wa_ir::ParsedFieldType::Bool +} + /// Whether a shape carries any of the constraint layer this guard exists to protect. fn is_constrained(assertions: &[ResponseAssertion], fields: &[ParsedField]) -> bool { if assertions @@ -435,3 +447,45 @@ fn a_pin_on_a_nested_node_is_checked_where_it_lives() { assert_eq!(broken.len(), 1, "{broken:?}"); assert!(broken[0].contains("participant/@type"), "{broken:?}"); } + +#[test] +fn a_pin_on_a_presence_flag_is_never_required() { + // `{hasListCDhash: m.value != null}` reports whether an OPTIONAL `c_dhash` attribute + // was there; the flag itself is always produced. An emitter must be free to omit the + // attribute — and must never be asked to compare the request's string against the + // boolean. + let flag = ParsedField { + method: String::new(), + name: "hasListCDhash".into(), + wire_name: Some("c_dhash".into()), + field_type: wa_ir::ParsedFieldType::Bool, + required: true, + reference_path: Some(vec!["item".into(), "dhash".into()]), + ..Default::default() + }; + let assertions = vec![ResponseAssertion { + kind: AssertionKind::Tag, + name: Some("iq".into()), + value: None, + reference_path: None, + }]; + let fields = vec![flag]; + let s = emit(&assertions, &fields); + assert!( + s.attr(&[], "c_dhash").is_none(), + "an optional attribute must not be forced onto the stanza" + ); + assert!( + violations(&s, &assertions, &fields).is_empty(), + "omitting it is legal" + ); + // Present but contradicting the request is still caught. + let mut wrong = emit(&assertions, &fields); + wrong + .nodes + .entry(Vec::new()) + .or_default() + .insert("c_dhash".into(), "not-the-request-value".into()); + let broken = violations(&wrong, &assertions, &fields); + assert_eq!(broken.len(), 1, "{broken:?}"); +} diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index 412e152..fa907e5 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -755,8 +755,15 @@ struct Counts { iq_field_enum_refs: usize, iq_typed_error_variants: usize, iq_error_texts: usize, + iq_error_arms: usize, /// Notification payload action-union arms recovered (`diagnostics.notif.actions`). notif_actions: usize, + /// Shape-level coverage of those arms: resolved `actionType`s plus every field, + /// constant and child field across them. The arm COUNT alone cannot regress while + /// the child-tag switch is still recognized — each case still yields one arm even if + /// its whole shape disappeared — so the layer could silently empty out inside a + /// steady count. This is the number that actually moves when extraction breaks. + notif_action_shapes: usize, /// Outgoing non-IQ stanzas (receipt/presence/chatstate/ack) the scanner recovers. stanza_defs: usize, /// Incoming content-stanza read-shapes (message/receipt/call/ack) recovered from @@ -821,6 +828,9 @@ struct IqConstraintCounts { typed_error_variants: usize, /// Distinct `` values across every RPC's error vocabulary. error_texts: usize, + /// Accepted `(code, text)` error shapes across every RPC — the paired form, which + /// the flattened `errorTexts` count cannot regress on its own. + error_arms: usize, } /// Count the validation constraints in an emitted IQ IR (see [`IqConstraintCounts`]). @@ -870,6 +880,7 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { c.typed_error_variants += 1; } texts.extend(v.error_texts.iter().cloned()); + c.error_arms += v.error_arms.len(); walk_fields(&v.fields, &mut c); } } @@ -1617,7 +1628,8 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou let (abprops_arts, abprops_count) = abprops.expect(checked); let (enums_arts, enums_count) = enums.expect(checked); let (wam_arts, wam_count) = wam.expect(checked); - let (notif_arts, (notif_count, notif_typed, notif_tags, notif_actions)) = notif.expect(checked); + let (notif_arts, (notif_count, notif_typed, notif_tags, notif_actions, notif_action_shapes)) = + notif.expect(checked); let (stanza_arts, stanza_count) = stanza.expect(checked); let (tokens_arts, (token_single, token_double)) = tokens.expect(checked); let (incoming_arts, incoming_count) = incoming.expect(checked); @@ -1691,7 +1703,9 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou iq_field_enum_refs: iq_diag.constraints.field_enum_refs, iq_typed_error_variants: iq_diag.constraints.typed_error_variants, iq_error_texts: iq_diag.constraints.error_texts, + iq_error_arms: iq_diag.constraints.error_arms, notif_actions, + notif_action_shapes, stanza_defs: stanza_count, incoming_defs: incoming_count, server_request_defs: srvreq_count, @@ -1798,6 +1812,7 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou "fieldEnumRefs": iq_diag.constraints.field_enum_refs, "typedErrorVariants": iq_diag.constraints.typed_error_variants, "errorTexts": iq_diag.constraints.error_texts, + "errorArms": iq_diag.constraints.error_arms, }, }, "notif": { @@ -1806,6 +1821,7 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou "degraded": counts.notif_types - counts.notif_typed_content, "stanzaTags": counts.notif_stanza_tags, "actions": counts.notif_actions, + "actionShapes": counts.notif_action_shapes, }, }, }); @@ -1886,6 +1902,7 @@ fn check_floor(out: &Path, counts: &Counts) -> Result> { ("fieldEnumRefs", counts.iq_field_enum_refs), ("typedErrorVariants", counts.iq_typed_error_variants), ("errorTexts", counts.iq_error_texts), + ("errorArms", counts.iq_error_arms), ] { if let Some(prev) = c.get(key).and_then(serde_json::Value::as_u64) && (new as u64) < prev @@ -1903,6 +1920,7 @@ fn check_floor(out: &Path, counts: &Counts) -> Result> { ("typedContent", counts.notif_typed_content), ("stanzaTags", counts.notif_stanza_tags), ("actions", counts.notif_actions), + ("actionShapes", counts.notif_action_shapes), ] { if let Some(prev) = notif.get(key).and_then(serde_json::Value::as_u64) && (new as u64) < prev @@ -2248,7 +2266,7 @@ fn push_notif( wa_version: &str, source: &str, module_defs: &[wa_transform::ModuleDefinition], -) -> Result<(usize, usize, usize, usize)> { +) -> Result<(usize, usize, usize, usize, usize)> { let ir = wa_notif::extract_notif_from_modules(source, module_defs, wa_version); let count = ir.notifications.len(); let stanza_tags = ir.stanza_tags.len(); @@ -2260,9 +2278,21 @@ fn push_notif( // Payload action-union arms (the `w:gp2` group actions and friends) — the layer // below the envelope, counted so it can't silently empty out. let actions: usize = ir.notifications.iter().map(|n| n.actions.len()).sum(); + let action_shapes: usize = ir + .notifications + .iter() + .flat_map(|n| &n.actions) + .map(|a| { + usize::from(a.action_type.is_some()) + + a.fields.len() + + a.constant_fields.len() + + a.children.iter().map(|c| 1 + c.fields.len()).sum::() + }) + .sum(); eprintln!( "notif: {count} notification types ({typed} with typed content, {} degraded), \ - {actions} payload action(s), {} stanza tags (dispatchers: {})", + {actions} payload action(s) / {action_shapes} shape element(s), \ + {} stanza tags (dispatchers: {})", count - typed, stanza_tags, if ir.dispatcher_modules.is_empty() { @@ -2281,7 +2311,7 @@ fn push_notif( rel_path: PathBuf::from("notif/notif.rs"), content: wa_codegen::generate_notif(&ir), }); - Ok((count, typed, stanza_tags, actions)) + Ok((count, typed, stanza_tags, actions, action_shapes)) } fn push_wam( diff --git a/generated/iq/index.json b/generated/iq/index.json index 8999247..53c304b 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -446,6 +446,18 @@ "tag": "GetExperimentConfigResponseErrorNoRetry", "moduleName": "WASmaxInAbPropsGetExperimentConfigResponseErrorNoRetry", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 501 @@ -580,6 +592,12 @@ "moduleName": "WASmaxInAbPropsGetExperimentConfigResponseErrorRetry", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -983,6 +1001,18 @@ "tag": "GetGroupExperimentConfigResponseErrorNoRetry", "moduleName": "WASmaxInAbPropsGetGroupExperimentConfigResponseErrorNoRetry", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 501 @@ -1117,6 +1147,12 @@ "moduleName": "WASmaxInAbPropsGetGroupExperimentConfigResponseErrorRetry", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -2029,6 +2065,18 @@ "moduleName": "WASmaxInBlocklistsGetBlockListResponseInvalidRequest", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 429 @@ -2163,6 +2211,18 @@ "moduleName": "WASmaxInBlocklistsGetBlockListResponseInternalServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500, 501 @@ -3014,6 +3074,33 @@ "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseInvalidRequest", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 403, @@ -3313,6 +3400,18 @@ "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500, 501 @@ -4093,6 +4192,28 @@ "tag": "BotListResponseError", "moduleName": "WASmaxInBotBotListResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 403, @@ -5599,6 +5720,23 @@ "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseRequestError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNoValidJID", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 406 @@ -5762,6 +5900,18 @@ "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 503 ], @@ -6772,6 +6922,23 @@ "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseRequestError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNoValidJID", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 406 @@ -6935,6 +7102,18 @@ "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 503 ], @@ -7362,6 +7541,18 @@ "moduleName": "WASmaxInPreKeysAddResponseRequestError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 406 ], @@ -7510,6 +7701,18 @@ "moduleName": "WASmaxInPreKeysAddResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 503 ], @@ -7735,6 +7938,18 @@ "moduleName": "WASmaxInPreKeysDeleteResponseRequestError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 406 ], @@ -7883,6 +8098,18 @@ "moduleName": "WASmaxInPreKeysDeleteResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 503 ], @@ -8366,6 +8593,18 @@ "moduleName": "WASmaxInPreKeysSetResponseRequestError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 406 ], @@ -8514,6 +8753,18 @@ "moduleName": "WASmaxInPreKeysSetResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 503 ], @@ -9096,6 +9347,12 @@ "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseTooManyAttempts", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 431, + "text": "TOO_MANY_ATTEMPTS" + } + ], "errorCodes": [ 431 ], @@ -9172,6 +9429,12 @@ "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseIncorrectNonce", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 432, + "text": "INCORRECT_NONCE" + } + ], "errorCodes": [ 432 ], @@ -9247,6 +9510,28 @@ "tag": "GetAccessTokenAndSessionCookiesResponseError", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 403, @@ -9630,6 +9915,23 @@ "tag": "GetAccountNonceResponseError", "moduleName": "WASmaxInBizLinkingGetAccountNonceResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNoticeRequired", + "code": 475, + "text": "notice-required" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 400, 475, @@ -10807,6 +11109,12 @@ "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseForbidden", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 403, + "text": "forbidden" + } + ], "errorCodes": [ 403 ], @@ -10887,6 +11195,12 @@ "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -11143,6 +11457,28 @@ "tag": "RequestSilentNonceResponseError", "moduleName": "WASmaxInBizAccessTokenRequestSilentNonceResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 403, @@ -11514,6 +11850,28 @@ "tag": "SendAccountRecoveryNonceResponseError", "moduleName": "WASmaxInBizCtwaAdAccountSendAccountRecoveryNonceResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 403, @@ -12339,6 +12697,28 @@ "tag": "UploadAdMediaResponseError", "moduleName": "WASmaxInBizCtwaNativeAdUploadAdMediaResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 403, @@ -12710,6 +13090,18 @@ "tag": "GetCountryCodeResponseError", "moduleName": "WASmaxInMdGetCountryCodeResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 400, 500 @@ -12960,6 +13352,12 @@ "moduleName": "WASmaxInMdGetPasskeyRequestOptionsResponseError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -13241,6 +13639,18 @@ "tag": "CompanionFinishResponseError", "moduleName": "WASmaxInMdCompanionFinishResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + } + ], "errorCodes": [ 400, 500 @@ -13551,6 +13961,33 @@ "tag": "CompanionHelloResponseError", "moduleName": "WASmaxInMdCompanionHelloResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotAvailable", + "code": 452, + "text": "feature-not-available" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + } + ], "errorCodes": [ 400, 403, @@ -19673,6 +20110,33 @@ "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnavailableForLegalReasonsResponse", + "code": 451, + "text": "features-disabled" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 404, @@ -20029,6 +20493,12 @@ "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -25702,6 +26172,38 @@ "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnavailableForLegalReasonsResponse", + "code": 451, + "text": "features-disabled" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "NotAllowedIQErrorResponse", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 404, @@ -26124,6 +26626,12 @@ "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -26625,6 +27133,38 @@ "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnauthorizedIQErrorResponse", + "code": 401, + "text": "not-authorized" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "NotAllowedIQErrorResponse", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 401, @@ -27047,6 +27587,12 @@ "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -28263,6 +28809,33 @@ "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnavailableForLegalReasonsResponse", + "code": 451, + "text": "features-disabled" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 404, @@ -28619,6 +29192,12 @@ "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -29900,6 +30479,38 @@ "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnavailableForLegalReasonsResponse", + "code": 451, + "text": "features-disabled" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "NotAllowedIQErrorResponse", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 404, @@ -30322,6 +30933,12 @@ "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -30700,6 +31317,28 @@ "moduleName": "WASmaxInNewslettersMyAddOnsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnauthorizedIQErrorResponse", + "code": 401, + "text": "not-authorized" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 401, @@ -30990,6 +31629,12 @@ "moduleName": "WASmaxInNewslettersMyAddOnsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -31318,6 +31963,28 @@ "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnauthorizedIQErrorResponse", + "code": 401, + "text": "not-authorized" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 401, @@ -31608,6 +32275,12 @@ "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -31785,6 +32458,33 @@ "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "BadRequestIQErrorResponse", + "code": 400, + "text": "bad-request" + }, + { + "name": "UnavailableForLegalReasonsResponse", + "code": 451, + "text": "features-disabled" + }, + { + "name": "ItemNotFoundIQErrorResponse", + "code": 404, + "text": "item-not-found" + }, + { + "name": "SuspendedIQErrorResponse", + "code": 423, + "text": "locked" + }, + { + "name": "RateLimitedIQErrorResponse", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 404, @@ -32141,6 +32841,12 @@ "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -32517,6 +33223,18 @@ "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInvalidRequest", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 429 @@ -32651,6 +33369,18 @@ "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInternalServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500, 501 @@ -33239,6 +33969,28 @@ "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseInvalidRequest", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 403, @@ -33475,6 +34227,18 @@ "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500, 501 @@ -34132,6 +34896,33 @@ "tag": "GetContactBlacklistResponseError", "moduleName": "WASmaxInPrivacyGetContactBlacklistResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 400, 429, @@ -35246,6 +36037,23 @@ "tag": "SignCredentialResponseErrorNoRetry", "moduleName": "WASmaxInPrivatestatsSignCredentialResponseErrorNoRetry", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 501, @@ -35419,6 +36227,12 @@ "moduleName": "WASmaxInPrivatestatsSignCredentialResponseErrorRetry", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -35748,6 +36562,37 @@ "tag": "GroupReportResponseError", "moduleName": "WASmaxInSpamGroupReportResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorReportTokenValidationFail", + "code": 548 + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 403, @@ -36194,6 +37039,37 @@ "tag": "IndividualReportResponseError", "moduleName": "WASmaxInSpamIndividualReportResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorReportTokenValidationFail", + "code": 548 + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 403, @@ -36631,6 +37507,37 @@ "tag": "NewsletterReportResponseError", "moduleName": "WASmaxInSpamNewsletterReportResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorReportTokenValidationFail", + "code": 548 + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 403, @@ -37062,6 +37969,37 @@ "tag": "StatusReportResponseError", "moduleName": "WASmaxInSpamStatusReportResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorReportTokenValidationFail", + "code": 548 + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 403, @@ -37487,6 +38425,28 @@ "tag": "StatusReportV2ResponseError", "moduleName": "WASmaxInSpamStatusReportV2ResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 403, @@ -37927,6 +38887,12 @@ "moduleName": "WASmaxInUserNoticeGetDisclosureStageByIdsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 400, + "text": "bad-request" + } + ], "errorCodes": [ 400 ], @@ -37999,6 +38965,18 @@ "tag": "GetDisclosureStageByIdsResponseServerError", "moduleName": "WASmaxInUserNoticeGetDisclosureStageByIdsResponseServerError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 429, 500 @@ -38313,6 +39291,12 @@ "moduleName": "WASmaxInUserNoticeGetDisclosuresResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 400, + "text": "bad-request" + } + ], "errorCodes": [ 400 ], @@ -38385,6 +39369,18 @@ "tag": "GetDisclosuresResponseServerError", "moduleName": "WASmaxInUserNoticeGetDisclosuresResponseServerError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 429, 500 @@ -39442,6 +40438,38 @@ "tag": "SetPaymentsTOSv3ResponseError", "moduleName": "WASmaxInAccountSetPaymentsTOSv3ResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPayUpgradeRequired", + "code": 443, + "text": "upgrade-required" + }, + { + "name": "IQErrorConfigMismatch", + "code": 453, + "text": "config-mismatch" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + } + ], "errorCodes": [ 400, 403, @@ -40243,6 +41271,12 @@ "moduleName": "WASmaxInPushConfigSetResponseInternalServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500 ], @@ -40316,6 +41350,12 @@ "moduleName": "WASmaxInPushConfigSetResponseConflict", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "code": 409, + "text": "conflict" + } + ], "errorCodes": [ 409 ], @@ -41406,6 +42446,28 @@ "tag": "GetBusinessEligibilityResponseError", "moduleName": "WASmaxInBizMarketingMessageGetBusinessEligibilityResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 403, @@ -41800,6 +42862,28 @@ "tag": "GetPrivacySettingResponseError", "moduleName": "WASmaxInBizSettingsGetPrivacySettingResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 400, 500, @@ -42760,6 +43844,43 @@ "tag": "GetSMBMeteredMessagingCheckoutResponseError", "moduleName": "WASmaxInSmbMeteredMessagingAccountGetSMBMeteredMessagingCheckoutResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFeatureLimit", + "code": 482, + "text": "feature-limit" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 401, @@ -43271,6 +44392,28 @@ "tag": "SetPrivacySettingResponseError", "moduleName": "WASmaxInBizSettingsSetPrivacySettingResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 400, 500, @@ -44212,6 +45355,28 @@ "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseInvalidRequest", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 403, @@ -44448,6 +45613,18 @@ "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + } + ], "errorCodes": [ 500, 501 @@ -44682,6 +45859,23 @@ "tag": "EventResponseError", "moduleName": "WASmaxInInAppCommsEventResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 408, 500, @@ -44989,6 +46183,28 @@ "tag": "ChatBlockGetResponseServerError", "moduleName": "WASmaxInPsaChatBlockGetResponseServerError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 408, 429, @@ -45334,6 +46550,28 @@ "tag": "ChatBlockSetResponseServerError", "moduleName": "WASmaxInPsaChatBlockSetResponseServerError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 408, 429, @@ -49038,6 +50276,18 @@ "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 429 @@ -49172,6 +50422,28 @@ "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -52618,6 +53890,38 @@ "moduleName": "WASmaxInGroupsGetGroupInfoResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorGone", + "code": 410, + "text": "gone" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 403, @@ -52898,6 +54202,28 @@ "moduleName": "WASmaxInGroupsGetGroupInfoResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -53687,6 +55013,38 @@ "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 403, @@ -53967,6 +55325,28 @@ "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -56505,6 +57885,53 @@ "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorGone", + "code": 410, + "text": "gone" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorGrowthLocked", + "code": 436, + "text": "growth-locked" + }, + { + "name": "IQErrorParentGroupSuspended", + "code": 416, + "text": "parent-group-suspended" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -56951,6 +58378,28 @@ "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -59712,6 +61161,33 @@ "moduleName": "WASmaxInGroupsGetLinkedGroupResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 403, @@ -59953,6 +61429,28 @@ "moduleName": "WASmaxInGroupsGetLinkedGroupResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -60367,6 +61865,28 @@ "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 401, @@ -60579,6 +62099,28 @@ "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -61237,6 +62779,43 @@ "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -61556,6 +63135,28 @@ "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -65121,6 +66722,23 @@ "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 429 @@ -65284,6 +66902,28 @@ "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -65834,6 +67474,33 @@ "moduleName": "WASmaxInGroupsGetReportedMessagesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + } + ], "errorCodes": [ 400, 401, @@ -66085,6 +67752,28 @@ "moduleName": "WASmaxInGroupsGetReportedMessagesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -66600,6 +68289,73 @@ "tag": "AcceptGroupAddResponseClientError", "moduleName": "WASmaxInGroupsAcceptGroupAddResponseClientError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorAlreadyExists", + "code": 304, + "text": "already-exists" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorGone", + "code": 410, + "text": "gone" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorResourceConstraint", + "code": 500, + "text": "resource-constraint" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorParentLinkedGroupsParticipantsResourceLimit", + "code": 412, + "text": "linked-groups-participants-resource-limit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 304, 400, @@ -67153,6 +68909,28 @@ "moduleName": "WASmaxInGroupsAcceptGroupAddResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -67435,6 +69213,28 @@ "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 404, @@ -67637,6 +69437,28 @@ "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -69259,6 +71081,59 @@ "moduleName": "WASmaxInGroupsAddParticipantsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "AddParticipantTimeRateLimit" + }, + { + "name": "AddParticipantCountRateLimit" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorParentGroupSuspended", + "code": 416, + "text": "parent-group-suspended" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -69763,6 +71638,28 @@ "moduleName": "WASmaxInGroupsAddParticipantsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -70303,6 +72200,33 @@ "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 404, @@ -70544,6 +72468,28 @@ "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -73450,6 +75396,69 @@ "tag": "CreateResponseClientError", "moduleName": "WASmaxInGroupsCreateResponseClientError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorResourceConstraint", + "code": 500, + "text": "resource-constraint" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "AddParticipantTimeRateLimit" + }, + { + "name": "AddParticipantCountRateLimit" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorParentLinkedGroupsParticipantsResourceLimit", + "code": 412, + "text": "linked-groups-participants-resource-limit" + }, + { + "name": "IQErrorParentGroupSuspended", + "code": 416, + "text": "parent-group-suspended" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 403, @@ -74056,6 +76065,28 @@ "moduleName": "WASmaxInGroupsCreateResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -74824,6 +76855,48 @@ "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 403, @@ -75206,6 +77279,28 @@ "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -75488,6 +77583,23 @@ "moduleName": "WASmaxInGroupsDeleteParentGroupResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + } + ], "errorCodes": [ 400, 401, @@ -75661,6 +77773,28 @@ "moduleName": "WASmaxInGroupsDeleteParentGroupResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -75993,6 +78127,63 @@ "tag": "JoinLinkedGroupResponseClientError", "moduleName": "WASmaxInGroupsJoinLinkedGroupResponseClientError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorUpgradeRequired", + "code": 426, + "text": "upgrade-required" + }, + { + "name": "IQErrorAlreadyExists", + "code": 304, + "text": "already-exists" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 304, 400, @@ -76492,6 +78683,28 @@ "moduleName": "WASmaxInGroupsJoinLinkedGroupResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -77398,6 +79611,58 @@ "moduleName": "WASmaxInGroupsLinkSubGroupsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorPayloadTooLarge", + "code": 413, + "text": "payload-too-large" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorParentGroupSuspended", + "code": 416, + "text": "parent-group-suspended" + }, + { + "name": "IQErrorGroupHasPendingAddRequests", + "code": 433, + "text": "pending-add-requests" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + } + ], "errorCodes": [ 400, 401, @@ -77844,6 +80109,28 @@ "moduleName": "WASmaxInGroupsLinkSubGroupsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -79121,6 +81408,38 @@ "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -79401,6 +81720,28 @@ "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -79920,6 +82261,48 @@ "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorBlockedIntegrityEnforcement", + "code": 486, + "text": "blocked-integrity-enforcement" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -80278,6 +82661,28 @@ "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -80919,6 +83324,48 @@ "moduleName": "WASmaxInGroupsPromoteDemoteResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorBlockedIntegrityEnforcement", + "code": 486, + "text": "blocked-integrity-enforcement" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -81277,6 +83724,28 @@ "moduleName": "WASmaxInGroupsPromoteDemoteResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -81973,6 +84442,48 @@ "moduleName": "WASmaxInGroupsRemoveParticipantsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorPayloadTooLarge", + "code": 413, + "text": "payload-too-large" + }, + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -82331,6 +84842,28 @@ "moduleName": "WASmaxInGroupsRemoveParticipantsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -82626,6 +85159,33 @@ "moduleName": "WASmaxInGroupsReportMessagesResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + } + ], "errorCodes": [ 400, 403, @@ -82877,6 +85437,28 @@ "moduleName": "WASmaxInGroupsReportMessagesResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -83353,6 +85935,38 @@ "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -83633,6 +86247,28 @@ "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -83953,6 +86589,48 @@ "moduleName": "WASmaxInGroupsSetDescriptionResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -84335,6 +87013,28 @@ "moduleName": "WASmaxInGroupsSetDescriptionResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -85101,6 +87801,58 @@ "moduleName": "WASmaxInGroupsSetPropertyResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorResourceLimit", + "code": 419, + "text": "resource-limit" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorUpgradeRequired", + "code": 426, + "text": "upgrade-required" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -85537,6 +88289,28 @@ "moduleName": "WASmaxInGroupsSetPropertyResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -85819,6 +88593,48 @@ "moduleName": "WASmaxInGroupsSetSubjectResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorForbidden", + "code": 403, + "text": "forbidden" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorLocked", + "code": 423, + "text": "locked" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -86201,6 +89017,28 @@ "moduleName": "WASmaxInGroupsSetSubjectResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -87425,6 +90263,33 @@ "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorFallbackClient", + "codeMin": 400, + "codeMax": 499 + } + ], "errorCodes": [ 400, 401, @@ -87666,6 +90531,28 @@ "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -88356,6 +91243,28 @@ "moduleName": "WASmaxInGroupsUnlinkGroupsResponseClientError", "kind": "error", "errorClass": "client", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + } + ], "errorCodes": [ 400, 401, @@ -88592,6 +91501,28 @@ "moduleName": "WASmaxInGroupsUnlinkGroupsResponseServerError", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorPartialServerError", + "code": 530, + "text": "partial-server-error" + }, + { + "name": "IQErrorFallbackServer", + "codeMin": 500, + "codeMax": 599 + } + ], "errorCodes": [ 500, 503, @@ -94122,6 +97053,43 @@ "tag": "GetResponseError", "moduleName": "WASmaxInProfilePictureGetResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorItemNotFound", + "code": 404, + "text": "item-not-found" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 400, 401, @@ -94697,6 +97665,23 @@ "tag": "SendBufferResponseErrorNoRetry", "moduleName": "WASmaxInStatsSendBufferResponseErrorNoRetry", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorNotAcceptable", + "code": 406, + "text": "not-acceptable" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 406, @@ -94894,6 +97879,12 @@ "moduleName": "WASmaxInStatsSendBufferResponseErrorRetry", "kind": "error", "errorClass": "server", + "errorArms": [ + { + "code": 503, + "text": "service-unavailable" + } + ], "errorCodes": [ 503 ], @@ -95347,6 +98338,68 @@ "tag": "EncryptedPayloadRequestResponseError", "moduleName": "WASmaxInWaffleEncryptedPayloadRequestResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorPayloadEncDec", + "code": 480, + "text": "payload-enc-dec-failed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorWFNotFound", + "code": 483, + "text": "wf-not-found" + }, + { + "name": "IQErrorWFSuspended", + "code": 484, + "text": "wf-suspended" + }, + { + "name": "IQErrorWFStateMismatch", + "code": 485, + "text": "wf-state-mismatch" + } + ], "errorCodes": [ 400, 401, @@ -95999,6 +99052,38 @@ "tag": "ForceDeleteStateResponseError", "moduleName": "WASmaxInWaffleForceDeleteStateResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 405, @@ -96414,6 +99499,53 @@ "tag": "ForceSuspendStateResponseError", "moduleName": "WASmaxInWaffleForceSuspendStateResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorWFNotFound", + "code": 483, + "text": "wf-not-found" + }, + { + "name": "IQErrorWFStateMismatch", + "code": 485, + "text": "wf-state-mismatch" + } + ], "errorCodes": [ 400, 405, @@ -97076,6 +100208,73 @@ "tag": "GenerateAccessTokensResponseError", "moduleName": "WASmaxInWaffleGenerateAccessTokensResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorPayloadEncDec", + "code": 480, + "text": "payload-enc-dec-failed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorAlreadyExists", + "code": 304, + "text": "already-exists" + }, + { + "name": "IQErrorWFNotFound", + "code": 483, + "text": "wf-not-found" + }, + { + "name": "IQErrorWFStateMismatch", + "code": 485, + "text": "wf-state-mismatch" + }, + { + "name": "IQErrorWFNotAuthorizedInvalidPassword", + "code": 416, + "text": "wf-not-authorized-invalid-password" + } + ], "errorCodes": [ 304, 400, @@ -97891,6 +101090,63 @@ "tag": "GenerateWAEntACUserResponseError", "moduleName": "WASmaxInWaffleGenerateWAEntACUserResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorPayloadEncDec", + "code": 480, + "text": "payload-enc-dec-failed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorAlreadyExists", + "code": 304, + "text": "already-exists" + }, + { + "name": "IQErrorWFJurisdictionGating", + "code": 418, + "text": "wf-jurisdiction-gating" + } + ], "errorCodes": [ 304, 400, @@ -98667,6 +101923,43 @@ "tag": "GetCertificateResponseError", "moduleName": "WASmaxInWaffleGetCertificateResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorFeatureNotImplemented", + "code": 501, + "text": "feature-not-implemented" + } + ], "errorCodes": [ 400, 405, @@ -99227,6 +102520,73 @@ "tag": "RefreshAccessTokensResponseError", "moduleName": "WASmaxInWaffleRefreshAccessTokensResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorPayloadEncDec", + "code": 480, + "text": "payload-enc-dec-failed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorWFNotFound", + "code": 483, + "text": "wf-not-found" + }, + { + "name": "IQErrorWFSuspended", + "code": 484, + "text": "wf-suspended" + }, + { + "name": "IQErrorWFStateMismatch", + "code": 485, + "text": "wf-state-mismatch" + }, + { + "name": "IQErrorWFNotAuthorizedInvalidPassword", + "code": 416, + "text": "wf-not-authorized-invalid-password" + } + ], "errorCodes": [ 400, 401, @@ -99991,6 +103351,38 @@ "tag": "StateExistsResponseError", "moduleName": "WASmaxInWaffleStateExistsResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + } + ], "errorCodes": [ 400, 405, @@ -100390,6 +103782,68 @@ "tag": "WFPingResponseError", "moduleName": "WASmaxInWaffleWFPingResponseError", "kind": "error", + "errorArms": [ + { + "name": "IQErrorBadRequest", + "code": 400, + "text": "bad-request" + }, + { + "name": "IQErrorRequestTimeout", + "code": 408, + "text": "request-timeout" + }, + { + "name": "IQErrorPayloadEncDec", + "code": 480, + "text": "payload-enc-dec-failed" + }, + { + "name": "IQErrorInternalServerError", + "code": 500, + "text": "internal-server-error" + }, + { + "name": "IQErrorServiceUnavailable", + "code": 503, + "text": "service-unavailable" + }, + { + "name": "IQErrorNotAuthorized", + "code": 401, + "text": "not-authorized" + }, + { + "name": "IQErrorRateOverlimit", + "code": 429, + "text": "rate-overlimit" + }, + { + "name": "IQErrorNotAllowed", + "code": 405, + "text": "not-allowed" + }, + { + "name": "IQErrorConflict", + "code": 409, + "text": "conflict" + }, + { + "name": "IQErrorWFNotFound", + "code": 483, + "text": "wf-not-found" + }, + { + "name": "IQErrorWFSuspended", + "code": 484, + "text": "wf-suspended" + }, + { + "name": "IQErrorWFStateMismatch", + "code": 485, + "text": "wf-state-mismatch" + } + ], "errorCodes": [ 400, 401, diff --git a/generated/manifest.json b/generated/manifest.json index 9a0fbfc..05eb5a2 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,6 +5,7 @@ "iq": { "candidateModules": 207, "constraints": { + "errorArms": 639, "errorTexts": 38, "fieldEnumRefs": 146, "fieldLiterals": 1782, @@ -20,7 +21,7 @@ "dropsByReason": { "iq builder substring present but no AST iq call": 5, "mixin fragment (folded into requests, not a standalone stanza)": 66, - "response enum argument not structurally resolvable": 65 + "response enum argument not structurally resolvable": 10 }, "excludedFragments": 66, "stanzas": 143, @@ -28,7 +29,8 @@ "unparseable": 5 }, "notif": { - "actions": 55, + "actionShapes": 181, + "actions": 58, "degraded": 5, "stanzaTags": 13, "typedContent": 22, @@ -59,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "1c8b2050b6b27927a03a45df37635e761a4963d2352439b6b700f92f7741503c" + "sha256": "9649dc834505ed964ad63bf1048d1239f30e2f97a12de9c9b49de2ec58000c02" }, "mex": { "file": "mex/index.json", @@ -69,7 +71,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "9763145bb76f08a005f9161cc211956f79e198388e0a4a728caf37f4a20bcb4a" + "sha256": "321066e135bdba60a6c0d2bab91f9bc5c12505d54aa67bd287e41c20810405cb" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index 9affb81..3e3e1cf 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -2283,6 +2283,14 @@ { "wireTag": "locked", "actionType": "restrict", + "fields": [ + { + "name": "threshold", + "wireName": "threshold", + "type": "string", + "required": false + } + ], "constantFields": [ { "name": "value", @@ -2485,6 +2493,95 @@ } ] }, + { + "wireTag": "unlink", + "actionType": "integrity_parent_group_unlink", + "children": [ + { + "name": "groupDatas", + "wireTag": "group", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "subjectTime", + "wireName": "s_t", + "type": "integer", + "required": true + } + ] + } + ] + }, + { + "wireTag": "unlink", + "actionType": "integrity_sub_group_unlink", + "children": [ + { + "name": "groupDatas", + "wireTag": "group", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "subjectTime", + "wireName": "s_t", + "type": "integer", + "required": true + } + ] + } + ] + }, + { + "wireTag": "unlink", + "children": [ + { + "name": "groupDatas", + "wireTag": "group", + "fields": [ + { + "name": "id", + "wireName": "jid", + "type": "group_jid", + "required": true + }, + { + "name": "subject", + "wireName": "subject", + "type": "string", + "required": true + }, + { + "name": "subjectTime", + "wireName": "s_t", + "type": "integer", + "required": true + } + ] + } + ] + }, { "wireTag": "membership_approval_mode", "actionType": "membership_approval_mode", diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index 8399ed1..71fff7b 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -99,6 +99,50 @@ "nodes" ] }, + "ErrorArm": { + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "type": "object", + "properties": { + "code": { + "description": "The exact `code` this arm pins, when it pins one.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMax": { + "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMin": { + "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "name": { + "description": "The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the\nerror is parsed as a `…Errors` union. `None` for an RPC that parses a single\n`` child directly, which has exactly one arm and no name for it.", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "type": [ + "string", + "null" + ] + } + } + }, "ErrorClass": { "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ @@ -233,7 +277,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" @@ -247,7 +291,7 @@ "type": "string" }, "referencePath": { - "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`],\nand — like it — always conditional when carried on a [`ParsedFieldType::Bool`]\npresence flag.\n\n[`literal_value`]: ParsedField::literal_value", "type": [ "array", "null" @@ -478,6 +522,13 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorArms": { + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "type": "array", + "items": { + "$ref": "#/$defs/ErrorArm" + } + }, "errorClass": { "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", "anyOf": [ @@ -506,7 +557,7 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", @@ -514,7 +565,7 @@ } }, "errorTexts": { - "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", "items": { "type": "string" diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index 70b43dd..b7bf340 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -104,6 +104,50 @@ "nodes" ] }, + "ErrorArm": { + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "type": "object", + "properties": { + "code": { + "description": "The exact `code` this arm pins, when it pins one.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMax": { + "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMin": { + "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "name": { + "description": "The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the\nerror is parsed as a `…Errors` union. `None` for an RPC that parses a single\n`` child directly, which has exactly one arm and no name for it.", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "type": [ + "string", + "null" + ] + } + } + }, "ErrorClass": { "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ @@ -298,7 +342,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" @@ -312,7 +356,7 @@ "type": "string" }, "referencePath": { - "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`],\nand — like it — always conditional when carried on a [`ParsedFieldType::Bool`]\npresence flag.\n\n[`literal_value`]: ParsedField::literal_value", "type": [ "array", "null" @@ -543,6 +587,13 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorArms": { + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "type": "array", + "items": { + "$ref": "#/$defs/ErrorArm" + } + }, "errorClass": { "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", "anyOf": [ @@ -571,7 +622,7 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", @@ -579,7 +630,7 @@ } }, "errorTexts": { - "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", "items": { "type": "string" diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 8e00082..096fb70 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -115,6 +115,50 @@ "nodes" ] }, + "ErrorArm": { + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "type": "object", + "properties": { + "code": { + "description": "The exact `code` this arm pins, when it pins one.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMax": { + "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMin": { + "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "name": { + "description": "The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the\nerror is parsed as a `…Errors` union. `None` for an RPC that parses a single\n`` child directly, which has exactly one arm and no name for it.", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "type": [ + "string", + "null" + ] + } + } + }, "ErrorClass": { "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ @@ -397,7 +441,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" @@ -411,7 +455,7 @@ "type": "string" }, "referencePath": { - "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`],\nand — like it — always conditional when carried on a [`ParsedFieldType::Bool`]\npresence flag.\n\n[`literal_value`]: ParsedField::literal_value", "type": [ "array", "null" @@ -642,6 +686,13 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorArms": { + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "type": "array", + "items": { + "$ref": "#/$defs/ErrorArm" + } + }, "errorClass": { "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", "anyOf": [ @@ -670,7 +721,7 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", @@ -678,7 +729,7 @@ } }, "errorTexts": { - "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", "items": { "type": "string" diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 112fc65..0007106 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -99,6 +99,50 @@ "nodes" ] }, + "ErrorArm": { + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "type": "object", + "properties": { + "code": { + "description": "The exact `code` this arm pins, when it pins one.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMax": { + "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMin": { + "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "name": { + "description": "The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the\nerror is parsed as a `…Errors` union. `None` for an RPC that parses a single\n`` child directly, which has exactly one arm and no name for it.", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "type": [ + "string", + "null" + ] + } + } + }, "ErrorClass": { "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ @@ -200,7 +244,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" @@ -214,7 +258,7 @@ "type": "string" }, "referencePath": { - "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`],\nand — like it — always conditional when carried on a [`ParsedFieldType::Bool`]\npresence flag.\n\n[`literal_value`]: ParsedField::literal_value", "type": [ "array", "null" @@ -445,6 +489,13 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorArms": { + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "type": "array", + "items": { + "$ref": "#/$defs/ErrorArm" + } + }, "errorClass": { "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", "anyOf": [ @@ -473,7 +524,7 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", @@ -481,7 +532,7 @@ } }, "errorTexts": { - "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", "items": { "type": "string" diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index c700ff0..593b943 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -106,6 +106,50 @@ "incoming" ] }, + "ErrorArm": { + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "type": "object", + "properties": { + "code": { + "description": "The exact `code` this arm pins, when it pins one.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMax": { + "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "codeMin": { + "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "name": { + "description": "The disjunction alternative this arm comes from (`IQErrorBadRequest`), when the\nerror is parsed as a `…Errors` union. `None` for an RPC that parses a single\n`` child directly, which has exactly one arm and no name for it.", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "type": [ + "string", + "null" + ] + } + } + }, "ErrorClass": { "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ @@ -207,7 +251,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" @@ -221,7 +265,7 @@ "type": "string" }, "referencePath": { - "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`].\n\n[`literal_value`]: ParsedField::literal_value", + "description": "The field is pinned not to a constant but to **a value taken from the request** —\nthe same echo rule [`AssertionKind::Reference`] carries, in the same\n`attrStringFromReference` path form, but for a pin that is *not* a variant guard.\n\nA required echo (`literal(…, \"from\", ref.to)`) is recorded as an assertion, since\nit must hold for the variant to match at all. An **optional** one\n(`optionalLiteral(…, \"c_dhash\", ref.item.dhash)`) is not a guard — the attribute\nmay be absent — so it lives here instead: an emitter may omit it, but if it sends\nit, the value must be the request's. Mutually exclusive with [`literal_value`],\nand — like it — always conditional when carried on a [`ParsedFieldType::Bool`]\npresence flag.\n\n[`literal_value`]: ParsedField::literal_value", "type": [ "array", "null" @@ -452,6 +496,13 @@ "$ref": "#/$defs/ResponseAssertion" } }, + "errorArms": { + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "type": "array", + "items": { + "$ref": "#/$defs/ErrorArm" + } + }, "errorClass": { "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", "anyOf": [ @@ -480,7 +531,7 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", @@ -488,7 +539,7 @@ } }, "errorTexts": { - "description": "The `` values this variant accepts, sorted — the closed vocabulary\npaired with [`error_codes`]. Empty when only a fallback (any-text) arm applies.\n\n[`error_codes`]: ResponseVariant::error_codes", + "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", "items": { "type": "string" From bd585b64067f60552d1d1a1276ad03efc5435fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 18:18:50 -0300 Subject: [PATCH 05/37] review: inherit outer error pins, and fix the accessor/scope gaps round 3 left MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four more Codex findings on 28b8b14. All applied; two were live, two latent. A nested payload union erased its parent's pins. An error alternative can both pin a code/text and carry a further union of reasons that pin nothing: `CreateOrAddGroupRateLimitError` pins 429/rate-overlimit and nests `AddParticipantTimeRateLimit` / `AddParticipantCountRateLimit`. The nested leaves replaced the outer arm, so `errorArms` — documented as the authoritative form — published four entries with no usable pairing while the shape the server actually sends was absent. Nested arms now inherit the outer pins wherever they have none of their own, and the specific inner name still wins. 4 empty arms -> 0. Accessor aliases were typed as strings. `find_accessor` returns the raw method name and `wap::method_field_type` did not know `attrPhoneUserJid`, `maybeAttrPhoneUserJid`, `attrPhoneDeviceJid`, `attrJidEnum`, `attrDomainJid`, `attrLidJid`, `attrIntRange`, `attrStringEnum` or `attrEnumOrNullIfUnknown`, while `is_wire_accessor` deliberately accepts every `attr*` — so those fields were kept and silently mistyped. Live case: the group-notification `phone_number`, a PN user JID reported as `string`, which is precisely the conflation that function's own doc comment calls protocol-safety-critical. Fixed in the shared classifier rather than locally, since it is wrong everywhere; blast radius is nil — none of those spellings appears as a `method` in any committed domain today. Two latent gaps the previous round opened or left: - Returns are now collected through `if`/`else`, but the scope was still built from direct declarations only, so `if (c) { var id = child.attrString("id"); return {id} }` yielded a return whose `id` resolved to nothing. `var` is function-scoped, so bindings are now collected through the same control flow. - An expression-bodied arrow callback (`p => wid(p.attrUserJid("jid"))`) has no `return` — oxc stores the body as a lone expression — so a repeated child was catalogued with an empty field list despite the comment promising that shape. Handled for callbacks and for inlined helpers alike. Neither changes today's output (WA's minifier emits `function(){}`, and no arm declares inside a nested block), so both are covered by fixture cases instead. Determinism holds, schemas validate, codegen still byte-identical, and no --allow-shrink was needed. --- crates/wa-ir/src/wap.rs | 16 ++++- crates/wa-notif/src/actions.rs | 75 +++++++++++++++++------ crates/wa-notif/src/tests.rs | 46 +++++++++++++- crates/wa-scan/src/response_index.rs | 89 +++++++++++++++++++++++++++- generated/iq/index.json | 16 +++-- generated/manifest.json | 4 +- generated/notif/index.json | 16 ++--- 7 files changed, 226 insertions(+), 36 deletions(-) diff --git a/crates/wa-ir/src/wap.rs b/crates/wa-ir/src/wap.rs index df73bcb..f6ea9b4 100644 --- a/crates/wa-ir/src/wap.rs +++ b/crates/wa-ir/src/wap.rs @@ -115,6 +115,11 @@ pub fn method_field_type(m: &str) -> ParsedFieldType { // flavor — rather than collapsing to a bare `Jid` — is protocol-safety-critical: // a LID user JID and a PN user JID are different identities for the same person. ATTR_USER_JID | MAYBE_ATTR_USER_JID => ParsedFieldType::UserJid, + // The `phone*` spellings are the explicit-PN aliases of the plain user/device + // accessors. Missing them typed a PN user JID as a bare `string`, which is + // exactly the conflation the note above calls protocol-safety-critical. + "attrPhoneUserJid" | "maybeAttrPhoneUserJid" => ParsedFieldType::UserJid, + "attrPhoneDeviceJid" => ParsedFieldType::DeviceJid, ATTR_LID_USER_JID | MAYBE_ATTR_LID_USER_JID => ParsedFieldType::LidUserJid, ATTR_DEVICE_JID => ParsedFieldType::DeviceJid, ATTR_LID_DEVICE_JID => ParsedFieldType::LidDeviceJid, @@ -123,10 +128,19 @@ pub fn method_field_type(m: &str) -> ParsedFieldType { ATTR_CALL_JID => ParsedFieldType::CallJid, ATTR_BROADCAST_JID => ParsedFieldType::BroadcastJid, ATTR_STATUS_JID => ParsedFieldType::StatusJid, - ATTR_JID_WITH_TYPE => ParsedFieldType::JidTyped, + ATTR_JID_WITH_TYPE | "attrJidEnum" => ParsedFieldType::JidTyped, // `attrWapJid`/`attrChatJid`/`attrFromJid` accept more than one flavor // (a chat is a user or a group), so they stay a generic `Jid`. ATTR_WAP_JID | ATTR_CHAT_JID | ATTR_FROM_JID => ParsedFieldType::Jid, + // Multi-flavor accessors: a chat is a user OR a group, `attrDomainJid`/`attrLidJid` + // accept more than one server, so they stay a generic JID rather than a string. + "attrPhoneChatJid" | "attrDomainJid" | "attrLidJid" | "attrFromPhoneJid" => { + ParsedFieldType::Jid + } + // Range-checked integers and the enum accessors, whose raw spellings the legacy + // parsers use directly. + "attrIntRange" | "contentInt" => ParsedFieldType::Integer, + "attrStringEnum" | "contentStringEnum" | "attrEnumOrNullIfUnknown" => ParsedFieldType::Enum, CONTENT_BYTES => ParsedFieldType::Bytes, _ => ParsedFieldType::String, } diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 56f5d38..b08065e 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -398,6 +398,22 @@ fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec<&'b Express /// blocks, `try`) but never into a nested function or a nested `switch` — an arm or helper that /// writes `if (cond) return {actionType: A, …}; return {actionType: B, …}` exposes both /// shapes, where a direct-children-only scan would silently keep just the last. +/// The result shapes of a function expression, handling the implicit return of an +/// expression-bodied arrow (`p => userJidToUserWid(p.attrUserJid("jid"))`), whose body +/// oxc stores as a lone `ExpressionStatement` rather than a `return`. +fn fn_result_shapes<'b, 'a>(e: &'b Expression<'a>) -> Vec<&'b Expression<'a>> { + if let Expression::ArrowFunctionExpression(arrow) = e + && let Some(expr) = arrow.get_expression() + { + let mut out = Vec::new(); + collect_branches(expr, &mut out); + return out; + } + function_body_of(e) + .map(arm_result_shapes) + .unwrap_or_default() +} + fn collect_returns<'b, 'a>(stmts: &'b [Statement<'a>], out: &mut Vec<&'b Expression<'a>>) { for s in stmts { match s { @@ -441,16 +457,39 @@ type Scope<'b, 'a> = HashMap<&'b str, &'b Expression<'a>>; fn scope_bindings<'b, 'a>(stmts: &'b [Statement<'a>]) -> Scope<'b, 'a> { let mut out = Scope::new(); + collect_bindings(stmts, &mut out); + out +} + +/// Collect `var` bindings through control flow, mirroring [`collect_returns`]. +/// +/// `var` is function-scoped in JS, so a declaration inside an `if` block is in scope for +/// the whole function — and now that returns are collected from nested branches, their +/// locals have to be too, or `if (c) { var id = child.attrString("id"); return {id} }` +/// yields a return whose `id` resolves to nothing and is silently dropped. First binding +/// wins, so the walk order (source order) decides, not the recursion order. +fn collect_bindings<'b, 'a>(stmts: &'b [Statement<'a>], out: &mut Scope<'b, 'a>) { for s in stmts { - if let Statement::VariableDeclaration(decl) = s { - for d in &decl.declarations { - if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) { - out.insert(name.as_str(), init); + match s { + Statement::VariableDeclaration(decl) => { + for d in &decl.declarations { + if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) + { + out.entry(name.as_str()).or_insert(init); + } + } + } + Statement::BlockStatement(b) => collect_bindings(&b.body, out), + Statement::IfStatement(i) => { + collect_bindings(std::slice::from_ref(&i.consequent), out); + if let Some(alt) = &i.alternate { + collect_bindings(std::slice::from_ref(alt), out); } } + Statement::TryStatement(t) => collect_bindings(&t.block.body, out), + _ => {} } } - out } /// Follow a bare identifier through the scope to the expression bound to it. Bounded so @@ -509,15 +548,17 @@ fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec function_body_of(&es.expression), + let Some(func) = ret.program.body.iter().find_map(|s| match s { + Statement::ExpressionStatement(es) => Some(&es.expression), _ => None, }) else { return Vec::new(); }; - let scope = scope_bindings(stmts); + let scope = function_body_of(func) + .map(scope_bindings) + .unwrap_or_default(); let mut out: Vec = Vec::new(); - for shape in arm_result_shapes(stmts) { + for shape in fn_result_shapes(func) { for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { match out.iter_mut().find(|g| g.action_type == action.action_type) { Some(existing) => merge_action(existing, action), @@ -606,14 +647,16 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) if ret.panicked { return; } - let Some(stmts) = ret.program.body.iter().find_map(|s| match s { - Statement::ExpressionStatement(es) => function_body_of(&es.expression), + let Some(func) = ret.program.body.iter().find_map(|s| match s { + Statement::ExpressionStatement(es) => Some(&es.expression), _ => None, }) else { return; }; - let scope = scope_bindings(stmts); - for shape in arm_result_shapes(stmts) { + let scope = function_body_of(func) + .map(scope_bindings) + .unwrap_or_default(); + for shape in fn_result_shapes(func) { fold_shape(shape, &scope, def, ctx, depth + 1); } } @@ -928,10 +971,8 @@ fn collect_accessor_fields<'b, 'a>( // A callback that returns a bare value rather than an object // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the // field by, so the wire attribute names it — better than reporting no fields at all. - if out.is_empty() - && let Some(stmts) = function_body_of(e) - { - for shape in arm_result_shapes(stmts) { + if out.is_empty() { + for shape in fn_result_shapes(e) { if let Some((method, wire_name, content)) = find_accessor(shape, &scope) { out.push(NotifActionField { name: wire_name.clone(), diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index d2cd691..415c8e9 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -619,6 +619,7 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.ADD: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ADD, participants:y(e,t), reason:t.hasAttr("reason")?t.attrString("reason"):null}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUBJECT: + if (t.hasChild("delete")) { var sid = t.attrString("id"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:sid}; } return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SUBJECT, subject:t.attrString("subject")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration")}; @@ -631,7 +632,7 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.UNLINK: return I(t); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.REVOKE_INVITE: - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.REVOKE_INVITE, participants:t.mapChildrenWithTag("participant", function(p){ return {id:p.attrUserJid("jid"), expiration:p.attrInt("expiration")}; })}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.REVOKE_INVITE, participants:t.mapChildrenWithTag("participant", function(p){ return {id:p.attrUserJid("jid"), expiration:p.attrInt("expiration")}; }), owners:t.mapChildrenWithTag("owner", p=>o("WAWebJidToWid").userJidToUserWid(p.maybeAttrPhoneUserJid("phone_number")))}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DESC: return t.hasChild("delete") ? {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:t.attrString("id")} : {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_ADD, descId:t.attrString("id"), desc:t.child("body").contentString()}; @@ -812,3 +813,46 @@ fn a_helper_returning_several_actions_yields_several_arms() { .collect(); assert_eq!(unlink, ["desc_remove", "desc_add"]); } + +#[test] +fn a_branch_local_binding_is_in_scope_for_its_return() { + // `var` is function-scoped, so a declaration inside an `if` block is in scope for the + // return inside it. Now that returns are collected from nested branches, their locals + // have to be too — otherwise `if (c) { var sid = child.attrString("id"); return + // {descId: sid} }` yields a return whose `sid` resolves to nothing and is dropped. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let arm = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "subject" && a.action_type.as_deref() == Some("desc_remove")) + .expect("the branch arm"); + let f = arm + .fields + .iter() + .find(|f| f.name == "descId") + .expect("descId"); + assert_eq!(f.wire_name, "id"); +} + +#[test] +fn an_expression_bodied_arrow_callback_yields_its_field() { + // `p => wid(p.maybeAttrPhoneUserJid("phone_number"))` has no `return`: oxc stores the + // body as a lone expression, so a return-only scan reported the repeated child with an + // empty field list. The accessor also has to be typed as the PN user JID it is, not as + // a bare string. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let owners = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "revoke") + .expect("revoke arm") + .children + .iter() + .find(|c| c.name == "owners") + .expect("owners child"); + assert_eq!(owners.wire_tag, "owner"); + let f = owners.fields.first().expect("a field from the arrow body"); + assert_eq!(f.wire_name, "phone_number"); + assert_eq!(f.field_type, wa_ir::ParsedFieldType::UserJid); + assert!(!f.required, "maybeAttr* is optional"); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index f873d16..07ac96f 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -306,17 +306,27 @@ fn collect_union_arms(fields: &[ParsedField], out: &mut Vec, found: &m for f in fields { for uv in f.union_variants.iter().flatten() { *found = true; + // What THIS alternative pins itself, before descending. An alternative can + // both pin `429`/`rate-overlimit` and carry a further payload union of + // reasons (`CreateOrAddGroupRateLimitError` does), and the nested reasons pin + // nothing — so replacing the outer arm with them would publish two arms with + // no usable pairing and drop the shape the server actually sends. + let own = arm_pins(&uv.fields); let before = out.len(); let mut nested = false; collect_union_arms(&uv.fields, out, &mut nested); if nested { - // A union of unions: the inner alternative names are the specific ones, - // so only name the arms that have none yet. for a in &mut out[before..] { + // Inner names are the specific ones; the outer pins are inherited + // wherever the inner arm has none of its own. a.name.get_or_insert_with(|| uv.name.clone()); + a.code = a.code.or(own.code); + a.text = a.text.clone().or_else(|| own.text.clone()); + a.code_min = a.code_min.or(own.code_min); + a.code_max = a.code_max.or(own.code_max); } } else { - let mut arm = arm_pins(&uv.fields); + let mut arm = own; arm.name = Some(uv.name.clone()); out.push(arm); } @@ -587,6 +597,79 @@ mod tests { assert!(v("GetBarResponseSuccess").error_arms.is_empty()); } + #[test] + fn a_nested_payload_union_inherits_the_outer_error_pins() { + // An alternative can BOTH pin a code/text and carry a further payload union of + // reasons that pin nothing (`CreateOrAddGroupRateLimitError` does). Replacing the + // outer arm with the nested reasons publishes arms with no usable pairing and + // drops the shape the server actually sends, so the outer pins are inherited. + let bundle = ERROR_RPC.replace( + r#"__d("WASmaxInFooClientErrors",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxInFooIQErrorBadRequestMixin").parseIQErrorBadRequestMixin(e); + if(t.success) return o("WAResultOrError").makeResult({name:"IQErrorBadRequest",value:t.value}); + return o("WASmaxParseUtils").errorMixinDisjunction(e,["IQErrorBadRequest"],[t]); + } + l.parseClientErrors = e; + }), 98);"#, + r#"__d("WASmaxInFooTimeRateLimitMixin",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxParseUtils").assertTag(e, "error"); if(!t.success) return t; + var n = o("WASmaxParseUtils").attrString(e, "reason"); + return n.success ? o("WAResultOrError").makeResult({reason:n.value}) : n; + } + l.parseTimeRateLimitMixin = e; + }), 98); + __d("WASmaxInFooReasons",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxInFooTimeRateLimitMixin").parseTimeRateLimitMixin(e); + if(t.success) return o("WAResultOrError").makeResult({name:"TimeRateLimit",value:t.value}); + return o("WASmaxParseUtils").errorMixinDisjunction(e,["TimeRateLimit"],[t]); + } + l.parseReasons = e; + }), 98); + __d("WASmaxInFooRateLimitMixin",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxParseUtils").assertTag(e, "error"); if(!t.success) return t; + var n = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrString, e, "text", "rate-overlimit"); if(!n.success) return n; + var r = o("WASmaxParseUtils").literal(o("WASmaxParseUtils").attrInt, e, "code", 429); if(!r.success) return r; + var q = o("WASmaxInFooReasons").parseReasons(e); + return q.success ? o("WAResultOrError").makeResult({text:n.value,code:r.value,reason:q.value}) : q; + } + l.parseRateLimitMixin = e; + }), 98); + __d("WASmaxInFooClientErrors",["WASmaxParseUtils"],(function(t,n,r,o,a,i,l){ + function e(e){ + var t = o("WASmaxInFooRateLimitMixin").parseRateLimitMixin(e); + if(t.success) return o("WAResultOrError").makeResult({name:"RateLimitError",value:t.value}); + return o("WASmaxParseUtils").errorMixinDisjunction(e,["RateLimitError"],[t]); + } + l.parseClientErrors = e; + }), 98);"#, + ); + let defs = wa_transform::extract_module_definitions(&bundle); + let idx = build_pass(&defs, &bundle); + let pr = idx.get_by_x("FooGetBar").expect("indexed by X"); + let ce = pr + .variants + .iter() + .find(|v| v.tag == "GetBarResponseClientError") + .expect("client error"); + assert!( + !ce.error_arms.is_empty(), + "the nested union must not erase the arm" + ); + for arm in &ce.error_arms { + assert_eq!( + (arm.code, arm.text.as_deref()), + (Some(429), Some("rate-overlimit")), + "every nested reason keeps the outer pins: {arm:?}" + ); + } + // The specific inner name wins over the outer one. + assert_eq!(ce.error_arms[0].name.as_deref(), Some("TimeRateLimit")); + } + #[test] fn error_class_is_derived_from_codes_not_from_the_variant_name() { // Codes decide, so `…ResponseInternalServerError` (which reads like a server diff --git a/generated/iq/index.json b/generated/iq/index.json index 53c304b..197315c 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -71103,10 +71103,14 @@ "text": "forbidden" }, { - "name": "AddParticipantTimeRateLimit" + "name": "AddParticipantTimeRateLimit", + "code": 429, + "text": "rate-overlimit" }, { - "name": "AddParticipantCountRateLimit" + "name": "AddParticipantCountRateLimit", + "code": 429, + "text": "rate-overlimit" }, { "name": "IQErrorRateOverlimit", @@ -75428,10 +75432,14 @@ "text": "bad-request" }, { - "name": "AddParticipantTimeRateLimit" + "name": "AddParticipantTimeRateLimit", + "code": 429, + "text": "rate-overlimit" }, { - "name": "AddParticipantCountRateLimit" + "name": "AddParticipantCountRateLimit", + "code": 429, + "text": "rate-overlimit" }, { "name": "IQErrorRateOverlimit", diff --git a/generated/manifest.json b/generated/manifest.json index 05eb5a2..fc00ec6 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -61,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "9649dc834505ed964ad63bf1048d1239f30e2f97a12de9c9b49de2ec58000c02" + "sha256": "95479b02b1d6826838a0084940de29317e60f9c774c926d066e3382023c186cc" }, "mex": { "file": "mex/index.json", @@ -71,7 +71,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "321066e135bdba60a6c0d2bab91f9bc5c12505d54aa67bd287e41c20810405cb" + "sha256": "58e04fb19b5c93259cbb7bf79ada3ccb5071b596f74613904a2234fa361ca4b3" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index 3e3e1cf..1ceb76a 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -1784,7 +1784,7 @@ { "name": "reason", "wireName": "reason", - "type": "string", + "type": "enum", "required": false }, { @@ -1824,7 +1824,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -1882,7 +1882,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -1936,7 +1936,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -1990,7 +1990,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -2052,7 +2052,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -2114,7 +2114,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { @@ -2168,7 +2168,7 @@ { "name": "phoneNumber", "wireName": "phone_number", - "type": "string", + "type": "user_jid", "required": false }, { From 1bf091cdb964b8691cd84190df1616db1ef42754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 18:46:03 -0300 Subject: [PATCH 06/37] review: per-return scopes, and call the schema bump what it is (2.0.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six findings on bd585b6 — one from CodeRabbit, five from Codex, two of them the same defect from both. All applied. schemaVersion 1.1.0 -> 2.0.0. Nearly everything this branch adds is optional and a 1.x consumer ignores it, but `AssertionKind` gained a `reference` variant, which widens the value space of an EXISTING field. A consumer with a closed enum — generated from the 1.0 JSON Schema, or the Rust enum itself — rejects the document rather than ignoring an unknown key, and that is not hypothetical: validating the current `iq/index.json` against the committed 1.0 schema fails with 579 errors, all `'reference' is not valid under any of the given schemas`. Shipping that as a minor bump would tell consumers the opposite of the truth. The migration is one variant wide, and the reasoning is recorded on the constant. A flattened branch scope produced WRONG field names, not missing ones. Making return collection recursive last round left the scope flat and first-wins, so mutually exclusive branches rebinding the same minified name made one branch's return read the other branch's attribute. Both reviewers flagged it; CodeRabbit proposed refusing the ambiguity, Codex proposed per-return scopes. Per-return is strictly better — it keeps both branches correct instead of dropping both — so shapes now carry the scope in force where they are returned. The one caller with no return path to attribute a binding to (the object-property walker) does refuse ambiguity, which is the "missing, not wrong" rule the rest of the module already follows. Also: - `guard && value` carries the value on the RIGHT; taking the left yielded `hasAttr`, deliberately not a wire accessor, so the field vanished. `a || b` keeps the left, where the value really is. - Notification action fields can carry `enumRef`, resolved through the same `wa_enums` path the request and response sides use, including when the enum argument is hoisted into a local. Currently latent in the bundle — the one live enum field (`create.reason`) validates against an ANONYMOUS inline table, whose identifier is whatever the minifier picked, so naming it would churn the IR every rollout and invent an identity the bundle does not have. Left unresolved and documented as such rather than faked. - Drops found while resolving a fallback response were being discarded: the fallback builds its own resolver during the scan, and the snapshot was taken before that from the build-pass resolver only. The collector is now shared and snapshotted after the scan, so the diagnostic guarantee holds for exactly the unusual shapes the fallback exists to serve. Determinism holds, schemas validate, codegen still byte-identical, no --allow-shrink. --- Cargo.lock | 1 + README.md | 2 + crates/wa-ir/src/lib.rs | 20 ++- crates/wa-ir/src/notif.rs | 14 ++ crates/wa-notif/Cargo.toml | 1 + crates/wa-notif/src/actions.rs | 232 ++++++++++++++++++++------- crates/wa-notif/src/tests.rs | 76 ++++++++- crates/wa-scan/src/lib.rs | 7 +- crates/wa-scan/src/response_index.rs | 24 ++- crates/wa-scan/src/response_smax.rs | 29 +++- generated/abprops/index.json | 2 +- generated/appstate/index.json | 2 +- generated/enums/index.json | 2 +- generated/incoming/index.json | 2 +- generated/iq/index.json | 2 +- generated/manifest.json | 26 +-- generated/mex/index.json | 2 +- generated/notif/index.json | 2 +- generated/schema/notif.schema.json | 11 ++ generated/srvreq/index.json | 2 +- generated/stanza/index.json | 2 +- generated/tokens/index.json | 2 +- generated/wam/index.json | 2 +- generated/wasm/index.json | 2 +- 24 files changed, 365 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c06136..5fc46dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1932,6 +1932,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "serde_json", + "wa-enums", "wa-ir", "wa-oxc", "wa-scan", diff --git a/README.md b/README.md index b438677..fb9bd7a 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Field shapes tell you how to *read* a stanza. They are not enough to *produce* o - **Response enums** (`enumRef` on a field) — the legal values behind an `attrStringEnum`, resolved the same way the request side already resolves them, instead of a bare `"type": "enum"`. - **Notification action unions** (`notifications[].actions`) — the payload inside the envelope. The `wireTag → actionType` mapping is **many-to-one** (`not_ephemeral` normalises into `ephemeral` with `duration: 0`, so branching on `not_ephemeral` is dead code) and field names are rebound (the disappearing-message timer arrives in `expiration`, but the action field is `duration`). Neither is derivable from the wire. +Note the contract version: this raised `schemaVersion` to **2.0.0**. Nearly every addition is optional and a 1.x consumer ignores it, but `AssertionKind` gained a `reference` variant, which widens the value space of an existing field — a consumer with a closed enum (generated from the 1.0 schema, or the Rust enum) rejects the document rather than ignoring it. The migration is one variant wide: handle, or ignore, `kind: "reference"` on response assertions. + Anything the extractor sees but cannot resolve structurally is counted under `manifest.diagnostics.iq.dropsByReason` rather than omitted, so "no constraint here" and "a constraint we failed to extract" never look alike. `manifest.diagnostics.iq.constraints` and `diagnostics.notif.actions` are floor-guarded: a WA refactor that hides one of these constructs fails the update instead of silently emptying a field. ## Quick start diff --git a/crates/wa-ir/src/lib.rs b/crates/wa-ir/src/lib.rs index 0f60869..59042e8 100644 --- a/crates/wa-ir/src/lib.rs +++ b/crates/wa-ir/src/lib.rs @@ -62,7 +62,25 @@ pub enum Scalar { /// `waVersion` changes on every WhatsApp rollout; `schemaVersion` changes only /// when the *structure* of the IR changes. Consumers pin this to stay stable /// across WhatsApp updates. Bump on any breaking change to the IR shape. -pub const SCHEMA_VERSION: &str = "1.1.0"; +/// +/// # 2.0.0 — why the major bump +/// +/// Almost everything the validation-constraint work added is additive and optional +/// (`literalValue`, `referencePath`, `enumRef`, `errorArms`, `actions`, …), which a 1.x +/// consumer ignores. **One** change is not: [`AssertionKind`] gained a `reference` +/// variant, and that widens the value space of an *existing* field. +/// +/// A consumer with a closed enum — one generated from the 1.0 JSON Schema, or the Rust +/// enum itself — rejects the document rather than ignoring an unknown key. This is not +/// hypothetical: validating the current `iq/index.json` against the committed 1.0 schema +/// fails with 579 errors, all `'reference' is not valid under any of the given schemas`. +/// Publishing that as a minor bump would tell consumers the opposite of the truth. +/// +/// The migration is one variant wide: handle (or ignore) `kind: "reference"` on response +/// assertions. Everything else in 2.0.0 is opt-in. +/// +/// [`AssertionKind`]: crate::AssertionKind +pub const SCHEMA_VERSION: &str = "2.0.0"; /// Envelope that stamps a domain IR document with [`SCHEMA_VERSION`] at emit /// time, without altering the inner document's shape. diff --git a/crates/wa-ir/src/notif.rs b/crates/wa-ir/src/notif.rs index b2d81ac..299bb02 100644 --- a/crates/wa-ir/src/notif.rs +++ b/crates/wa-ir/src/notif.rs @@ -200,6 +200,20 @@ pub struct NotifActionField { /// (`child("body").contentString()`). #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub content: bool, + /// The wire enum the value is validated against, when the arm reads it with an enum + /// accessor (`child.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES)`). + /// Without it a `"type": "enum"` field says the value is constrained but not to what, + /// which leaves a parser and an emitter guessing — the same gap `enumRef` closes on + /// the response side. + /// + /// Absent when the accessor validates against an **anonymous** lookup table declared + /// inline in the handler (`attrEnumOrNullIfUnknown("reason", v)` where `v` is a local + /// object literal). Such a table has no stable name — the identifier is whatever the + /// minifier picked this build — so naming it would churn the IR on every WhatsApp + /// rollout and invent an identity the bundle does not have. Reported as unresolved + /// rather than faked; recovering those value sets is a separate modelling question. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enum_ref: Option, } /// A `key → constant` pair an action arm stamps onto its result unconditionally. diff --git a/crates/wa-notif/Cargo.toml b/crates/wa-notif/Cargo.toml index a6e4356..abb8bed 100644 --- a/crates/wa-notif/Cargo.toml +++ b/crates/wa-notif/Cargo.toml @@ -14,6 +14,7 @@ oxc_ast = { workspace = true } oxc_ast_visit = { workspace = true } oxc_span = { workspace = true } oxc_syntax = { workspace = true } +wa-enums = { workspace = true } wa-ir = { workspace = true } wa-oxc = { workspace = true } wa-scan = { workspace = true } diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index b08065e..1a21154 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -70,6 +70,32 @@ impl<'a> ConstResolver<'a> { .and_then(|m| m.get(member).cloned()) } + /// Resolve the enum argument of an enum accessor — `o("Mod").ENUM_NAME`, a member + /// *reference* rather than a `.MEMBER` lookup — to its full variant set, reusing the + /// same resolver the request and response sides use so all three type an enum + /// attribute identically. + fn enum_ref(&self, e: &Expression) -> Option { + let (obj, name) = as_member(e)?; + let module = as_string_lit(arg_expr(as_call(obj)?.arguments.first()?)?)?; + let def = wa_enums::resolve_named_enum(self.slices.get(module)?, module, name)?; + let variants: Vec = def + .variants + .into_iter() + .map(|v| match v.value { + wa_ir::Scalar::Str(s) => Some(wa_ir::AttrEnumVariant { + name: v.name, + value: s, + }), + _ => None, + }) + .collect::>>()?; + (!variants.is_empty()).then(|| wa_ir::AttrEnumRef { + name: name.to_string(), + module: module.to_string(), + variants, + }) + } + /// Resolve a `o("Mod").OBJECT.MEMBER` expression, or `None` for anything else. fn resolve(&self, e: &Expression) -> Option { let (obj, member) = as_member(e)?; @@ -321,12 +347,8 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // So branches are grouped by `actionType`: different ones stay separate arms of // the union, identical ones merge into one (a field only some branches read is // optional in the merge). - let scope = scope_bindings(match consequent { - [Statement::BlockStatement(b)] => &b.body[..], - other => other, - }); let mut grouped: Vec = Vec::new(); - for shape in shapes { + for (shape, scope) in shapes { for action in expand_shape(&wire_tag, shape, &scope, ctx, 0) { match grouped .iter_mut() @@ -384,13 +406,21 @@ fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { /// Every distinct result shape an arm can return: one per branch of a `cond ? A : B` /// (nested ternaries included), each with its `babelHelpers.extends(…)` merged away. -fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec<&'b Expression<'a>> { +fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec> { + arm_result_shapes_in(consequent, &Scope::new()) +} + +/// As [`arm_result_shapes`], with an enclosing scope the branches layer over. +fn arm_result_shapes_in<'b, 'a>( + consequent: &'b [Statement<'a>], + outer: &Scope<'b, 'a>, +) -> Vec> { let stmts = match consequent { [Statement::BlockStatement(b)] => &b.body[..], other => other, }; let mut out = Vec::new(); - collect_returns(stmts, &mut out); + collect_returns(stmts, outer, &mut out); out } @@ -401,35 +431,55 @@ fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec<&'b Express /// The result shapes of a function expression, handling the implicit return of an /// expression-bodied arrow (`p => userJidToUserWid(p.attrUserJid("jid"))`), whose body /// oxc stores as a lone `ExpressionStatement` rather than a `return`. -fn fn_result_shapes<'b, 'a>(e: &'b Expression<'a>) -> Vec<&'b Expression<'a>> { +fn fn_result_shapes<'b, 'a>(e: &'b Expression<'a>, base: &Scope<'b, 'a>) -> Vec> { if let Expression::ArrowFunctionExpression(arrow) = e && let Some(expr) = arrow.get_expression() { - let mut out = Vec::new(); - collect_branches(expr, &mut out); - return out; + let mut branches = Vec::new(); + collect_branches(expr, &mut branches); + return branches.into_iter().map(|x| (x, base.clone())).collect(); } function_body_of(e) - .map(arm_result_shapes) + .map(|stmts| arm_result_shapes_in(stmts, base)) .unwrap_or_default() } -fn collect_returns<'b, 'a>(stmts: &'b [Statement<'a>], out: &mut Vec<&'b Expression<'a>>) { +fn collect_returns<'b, 'a>( + stmts: &'b [Statement<'a>], + outer: &Scope<'b, 'a>, + out: &mut Vec>, +) { + // This level's declarations, layered over the enclosing ones. Built per branch on + // purpose: mutually exclusive branches routinely rebind the same minified name to + // DIFFERENT accessors, and a single flattened scope would make one branch's return + // read the other branch's attribute — a wrong `wireName`, not a missing field. + let mut scope = outer.clone(); + for s in stmts { + if let Statement::VariableDeclaration(decl) = s { + for d in &decl.declarations { + if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) { + scope.insert(name.as_str(), init); + } + } + } + } for s in stmts { match s { Statement::ReturnStatement(r) => { if let Some(arg) = r.argument.as_ref() { - collect_branches(arg, out); + let mut branches = Vec::new(); + collect_branches(arg, &mut branches); + out.extend(branches.into_iter().map(|e| (e, scope.clone()))); } } - Statement::BlockStatement(b) => collect_returns(&b.body, out), + Statement::BlockStatement(b) => collect_returns(&b.body, &scope, out), Statement::IfStatement(i) => { - collect_returns(std::slice::from_ref(&i.consequent), out); + collect_returns(std::slice::from_ref(&i.consequent), &scope, out); if let Some(alt) = &i.alternate { - collect_returns(std::slice::from_ref(alt), out); + collect_returns(std::slice::from_ref(alt), &scope, out); } } - Statement::TryStatement(t) => collect_returns(&t.block.body, out), + Statement::TryStatement(t) => collect_returns(&t.block.body, &scope, out), // Deliberately NOT descending into a nested `switch`: that is a different // dispatch level, and its arms are other actions' shapes, not branches of // this one. @@ -455,9 +505,22 @@ fn collect_branches<'b, 'a>(e: &'b Expression<'a>, out: &mut Vec<&'b Expression< /// so without this most helper-built shapes would read as fieldless. type Scope<'b, 'a> = HashMap<&'b str, &'b Expression<'a>>; +/// A result shape together with the bindings in force where it is returned. +type Shape<'b, 'a> = (&'b Expression<'a>, Scope<'b, 'a>); + +/// A single flat scope for callers that have no return path to attribute a binding to +/// (the object-property walker visits every property in a callback at once). +/// +/// Without a path, a name rebound to a *different* initializer in a sibling branch +/// cannot be resolved correctly — so it is refused rather than guessed, the same +/// "missing, not wrong" rule the constant tables and helper names follow. fn scope_bindings<'b, 'a>(stmts: &'b [Statement<'a>]) -> Scope<'b, 'a> { let mut out = Scope::new(); - collect_bindings(stmts, &mut out); + let mut ambiguous: Vec<&str> = Vec::new(); + collect_bindings(stmts, &mut out, &mut ambiguous); + for name in ambiguous { + out.remove(name); + } out } @@ -468,25 +531,38 @@ fn scope_bindings<'b, 'a>(stmts: &'b [Statement<'a>]) -> Scope<'b, 'a> { /// locals have to be too, or `if (c) { var id = child.attrString("id"); return {id} }` /// yields a return whose `id` resolves to nothing and is silently dropped. First binding /// wins, so the walk order (source order) decides, not the recursion order. -fn collect_bindings<'b, 'a>(stmts: &'b [Statement<'a>], out: &mut Scope<'b, 'a>) { +fn collect_bindings<'b, 'a>( + stmts: &'b [Statement<'a>], + out: &mut Scope<'b, 'a>, + ambiguous: &mut Vec<&'b str>, +) { for s in stmts { match s { Statement::VariableDeclaration(decl) => { for d in &decl.declarations { if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) { - out.entry(name.as_str()).or_insert(init); + match out.entry(name.as_str()) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(init); + } + std::collections::hash_map::Entry::Occupied(e) => { + if !std::ptr::eq(*e.get(), init) { + ambiguous.push(name.as_str()); + } + } + } } } } - Statement::BlockStatement(b) => collect_bindings(&b.body, out), + Statement::BlockStatement(b) => collect_bindings(&b.body, out, ambiguous), Statement::IfStatement(i) => { - collect_bindings(std::slice::from_ref(&i.consequent), out); + collect_bindings(std::slice::from_ref(&i.consequent), out, ambiguous); if let Some(alt) = &i.alternate { - collect_bindings(std::slice::from_ref(alt), out); + collect_bindings(std::slice::from_ref(alt), out, ambiguous); } } - Statement::TryStatement(t) => collect_bindings(&t.block.body, out), + Statement::TryStatement(t) => collect_bindings(&t.block.body, out, ambiguous), _ => {} } } @@ -554,11 +630,8 @@ fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec = Vec::new(); - for shape in fn_result_shapes(func) { + for (shape, scope) in fn_result_shapes(func, &Scope::new()) { for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { match out.iter_mut().find(|g| g.action_type == action.action_type) { Some(existing) => merge_action(existing, action), @@ -615,7 +688,7 @@ fn fold_shape<'b, 'a>( // A helper whose whole result is a repeated element (`return // child.mapChildrenWithTag("participant", …)` — where every participant // list lives). The caller names it after the key it was bound to. - if let Some(child) = mapped_child("", e, scope) { + if let Some(child) = mapped_child("", e, scope, ctx.consts) { def.children.push(child); } else if let Some(src) = local_call_source(e, ctx) { inline_local(&src, def, ctx, depth); @@ -653,10 +726,7 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) }) else { return; }; - let scope = function_body_of(func) - .map(scope_bindings) - .unwrap_or_default(); - for shape in fn_result_shapes(func) { + for (shape, scope) in fn_result_shapes(func, &Scope::new()) { fold_shape(shape, &scope, def, ctx, depth + 1); } } @@ -699,7 +769,7 @@ fn fold_object<'b, 'a>( push_constant(def, key, c); continue; } - if let Some(child) = mapped_child(key, value, scope) { + if let Some(child) = mapped_child(key, value, scope, ctx.consts) { if !def.children.iter().any(|x| x.name == child.name) { def.children.push(child); } @@ -727,7 +797,7 @@ fn fold_object<'b, 'a>( } continue; } - if let Some(field) = read_field(key, value, scope) + if let Some(field) = read_field(key, value, scope, ctx.consts) && !def.fields.iter().any(|x| x.name == field.name) { def.fields.push(field); @@ -763,6 +833,7 @@ fn mapped_child<'b, 'a>( key: &str, e: &'b Expression<'a>, scope: &Scope<'b, 'a>, + consts: &ConstResolver, ) -> Option { let call = as_call(strip_guard(e))?; if callee_method(call)? != wap::MAP_CHILDREN_WITH_TAG { @@ -772,7 +843,7 @@ fn mapped_child<'b, 'a>( let mut fields = Vec::new(); for arg in &call.arguments { if let Some(e) = arg_expr(arg) { - collect_accessor_fields(e, scope, &mut fields); + collect_accessor_fields(e, scope, consts, &mut fields); } } Some(NotifActionChild { @@ -793,16 +864,18 @@ fn read_field<'b, 'a>( key: &str, e: &'b Expression<'a>, scope: &Scope<'b, 'a>, + consts: &ConstResolver, ) -> Option { let e = deref_ident(e, scope); let optional_by_guard = is_guarded(e); - let (method, wire_name, content) = find_accessor(strip_guard(e), scope)?; + let acc = find_accessor(strip_guard(e), scope)?; Some(NotifActionField { name: key.to_string(), - wire_name, - field_type: wap::method_field_type(&method), - required: !optional_by_guard && !wap::is_optional_method(&method), - content, + wire_name: acc.wire_name, + field_type: wap::method_field_type(&acc.method), + required: !optional_by_guard && !wap::is_optional_method(&acc.method), + content: acc.content, + enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), }) } @@ -831,7 +904,14 @@ fn strip_guard<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { } } Expression::ParenthesizedExpression(p) => strip_guard(&p.expression), - Expression::LogicalExpression(l) => strip_guard(&l.left), + // `guard && value` puts the value on the RIGHT — taking the left yields the + // `hasAttr` test, which is deliberately not a wire accessor, so the field would + // vanish. `a || b` / `a ?? b` are the opposite: the left IS the value and the + // right only defaults it. + Expression::LogicalExpression(l) => match l.operator { + oxc_syntax::operator::LogicalOperator::And => strip_guard(&l.right), + _ => strip_guard(&l.left), + }, Expression::AssignmentExpression(a) => strip_guard(&a.right), _ => e, } @@ -866,13 +946,19 @@ fn assigned_in<'b, 'a>(e: &'b Expression<'a>, name: &str) -> Option<&'b Expressi /// (`userJidToUserWid(child.attrUserJid("jid"))`, `S(child.attrString("reason"))`), and /// through a `child("body").contentString()` chain — where the *tag* is the wire name, /// since a content read has no attribute of its own. -fn find_accessor<'b, 'a>( - e: &'b Expression<'a>, - scope: &Scope<'b, 'a>, -) -> Option<(String, String, bool)> { +fn find_accessor<'b, 'a>(e: &'b Expression<'a>, scope: &Scope<'b, 'a>) -> Option> { find_accessor_at(e, scope, 0) } +/// A resolved wire read: the accessor method, the attribute (or child tag) it reads, +/// whether it is a content read, and the enum argument it validates against, if any. +struct Accessor<'b, 'a> { + method: String, + wire_name: String, + content: bool, + enum_arg: Option<&'b Expression<'a>>, +} + /// How deep the wrapper-call descent goes. Bounded like every other descent in this /// module: mutually referential minified bindings (`var a = g(b), b = g(a)` — legal JS, /// and the minifier reuses short names aggressively) would otherwise cycle through @@ -884,7 +970,7 @@ fn find_accessor_at<'b, 'a>( e: &'b Expression<'a>, scope: &Scope<'b, 'a>, depth: u8, -) -> Option<(String, String, bool)> { +) -> Option> { if depth > MAX_ACCESSOR_DEPTH { return None; } @@ -895,7 +981,23 @@ fn find_accessor_at<'b, 'a>( if let Some(name) = name && is_wire_accessor(method) { - return Some((method.to_string(), name.to_string(), false)); + // An enum accessor takes the enum as a further argument + // (`maybeAttrEnum("type", o("Mod").GROUP_PARTICIPANT_TYPES)`) — often hoisted + // into a local first (`attrEnumOrNullIfUnknown("reason", v)`), so the + // candidate is dereferenced through the scope before being recognised. + let enum_arg = call + .arguments + .iter() + .skip(1) + .filter_map(arg_expr) + .map(|a| deref_ident(a, scope)) + .find(|a| as_member(a).is_some()); + return Some(Accessor { + method: method.to_string(), + wire_name: name.to_string(), + content: false, + enum_arg, + }); } // `X.contentString()` / `X.contentInt()` — no argument; the wire name is the tag // of whatever `X` descends to, and `""` when it reads the arm's own node. @@ -913,7 +1015,12 @@ fn find_accessor_at<'b, 'a>( .and_then(|inner| inner.arguments.first().and_then(arg_expr)) .and_then(as_string_lit) .unwrap_or_default(); - return Some((method.to_string(), tag.to_string(), true)); + return Some(Accessor { + method: method.to_string(), + wire_name: tag.to_string(), + content: true, + enum_arg: None, + }); } } // A wrapper call (`userJidToUserWid(…)`, a local normaliser): look inside. @@ -936,6 +1043,7 @@ fn is_wire_accessor(method: &str) -> bool { fn collect_accessor_fields<'b, 'a>( e: &'b Expression<'a>, outer: &Scope<'b, 'a>, + consts: &ConstResolver, out: &mut Vec, ) { // The callback's own `var` bindings, layered over the enclosing scope: the minifier @@ -946,12 +1054,13 @@ fn collect_accessor_fields<'b, 'a>( } struct Walker<'o, 'b, 'a> { scope: &'o Scope<'b, 'a>, + consts: &'o ConstResolver<'o>, out: &'o mut Vec, } impl<'b, 'a> Visit<'a> for Walker<'_, 'b, 'a> { fn visit_object_property(&mut self, p: &oxc_ast::ast::ObjectProperty<'a>) { if let Some(key) = wa_oxc::property_key_name(&p.key) - && let Some(field) = read_field(key, &p.value, self.scope) + && let Some(field) = read_field(key, &p.value, self.scope, self.consts) && !self.out.iter().any(|f| f.name == field.name) { self.out.push(field); @@ -965,21 +1074,26 @@ fn collect_accessor_fields<'b, 'a>( e, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) ) { - let mut w = Walker { scope: &scope, out }; + let mut w = Walker { + scope: &scope, + consts, + out, + }; w.visit_expression(e); } // A callback that returns a bare value rather than an object // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the // field by, so the wire attribute names it — better than reporting no fields at all. if out.is_empty() { - for shape in fn_result_shapes(e) { - if let Some((method, wire_name, content)) = find_accessor(shape, &scope) { + for (shape, inner) in fn_result_shapes(e, &scope) { + if let Some(acc) = find_accessor(shape, &inner) { out.push(NotifActionField { - name: wire_name.clone(), - wire_name, - field_type: wap::method_field_type(&method), - required: !wap::is_optional_method(&method), - content, + name: acc.wire_name.clone(), + wire_name: acc.wire_name, + field_type: wap::method_field_type(&acc.method), + required: !wap::is_optional_method(&acc.method), + content: acc.content, + enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), }); } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 415c8e9..ad063ce 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -599,13 +599,17 @@ __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); +__d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ + var g={admin:"admin",superadmin:"superadmin",participant:"participant"}; + l.GROUP_PARTICIPANT_TYPES=g; +}), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); l.GROUP_ACTIONS=d; }), 1); __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ - return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name") }; + return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; }); } function I(e){ var t=e.attrString("unlink_type"); @@ -619,8 +623,9 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.ADD: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ADD, participants:y(e,t), reason:t.hasAttr("reason")?t.attrString("reason"):null}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUBJECT: - if (t.hasChild("delete")) { var sid = t.attrString("id"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:sid}; } - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SUBJECT, subject:t.attrString("subject")}; + if (t.hasChild("delete")) { var n = t.attrString("id"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, descId:n}; } + var n = t.attrString("subject"); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SUBJECT, subject:n, note:t.hasAttr("note")&&t.attrString("note")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: @@ -725,7 +730,7 @@ fn group_action_arms_recover_fields_children_and_branches() { .iter() .map(|f| f.name.as_str()) .collect(); - assert_eq!(names, ["id", "displayName"]); + assert_eq!(names, ["id", "displayName", "kind"]); assert_eq!( participants.fields[0].field_type, wa_ir::ParsedFieldType::UserJid @@ -856,3 +861,66 @@ fn an_expression_bodied_arrow_callback_yields_its_field() { assert_eq!(f.field_type, wa_ir::ParsedFieldType::UserJid); assert!(!f.required, "maybeAttr* is optional"); } + +#[test] +fn sibling_branches_rebinding_a_name_each_keep_their_own_accessor() { + // The minifier reuses short names across mutually exclusive branches. A single + // flattened scope would make one branch's return read the OTHER branch's attribute — + // a wrong `wireName`, not a missing field, which is the failure mode this module + // refuses everywhere else. Each return therefore resolves in its own scope. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let arms: Vec<_> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "subject") + .collect(); + let wire = |action: &str, field: &str| { + arms.iter() + .find(|a| a.action_type.as_deref() == Some(action)) + .and_then(|a| a.fields.iter().find(|f| f.name == field)) + .map(|f| f.wire_name.as_str()) + }; + // Both branches bind `n`, to different attributes. + assert_eq!(wire("desc_remove", "descId"), Some("id")); + assert_eq!(wire("subject", "subject"), Some("subject")); +} + +#[test] +fn a_logical_and_guard_reads_its_value_operand() { + // `child.hasAttr("note") && child.attrString("note")` carries the value on the RIGHT. + // Taking the left yields `hasAttr`, which is deliberately not a wire accessor, so the + // field disappeared. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let note = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "subject") + .find_map(|a| a.fields.iter().find(|f| f.name == "note")) + .expect("note field"); + assert_eq!(note.wire_name, "note"); +} + +#[test] +fn an_action_enum_field_carries_its_variants() { + // `maybeAttrEnum("type", o("Mod").GROUP_PARTICIPANT_TYPES)` — a bare `"type": "enum"` + // says the value is constrained but not to what, leaving an emitter to guess. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let kind = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "add") + .expect("add arm") + .children + .iter() + .find(|c| c.name == "participants") + .expect("participants") + .fields + .iter() + .find(|f| f.name == "kind") + .expect("kind field"); + assert_eq!(kind.field_type, wa_ir::ParsedFieldType::String); + let er = kind.enum_ref.as_ref().expect("resolved enum"); + assert_eq!(er.name, "GROUP_PARTICIPANT_TYPES"); + let values: Vec<&str> = er.variants.iter().map(|v| v.value.as_str()).collect(); + assert_eq!(values, ["admin", "superadmin", "participant"]); +} diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index 1278625..238ecef 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -158,7 +158,6 @@ pub fn scan_iq_with_diagnostics( // `WASmaxIn*ResponseSuccess` modules so a Request can attach its typed // response (the smax response lives in a separate module). let response_index = response_index::build_pass(module_defs, source); - let constraint_drops = response_index.drop_counts().clone(); // Build the content-length cross-reference once. It reads the byte length WA // Web's parsers pin each wire field to (`child("signature").contentBytes(64)`), @@ -292,7 +291,11 @@ pub fn scan_iq_with_diagnostics( unparseable, }, IqScanStats { - constraint_drops, + // Snapshotted only now: the request-anchored fallback builds its own resolver + // during the scan loop above and reports into the same shared collector, so + // reading it right after `build_pass` would miss every drop from precisely the + // unusual response shapes that fallback exists to handle. + constraint_drops: response_index.drop_counts(), ..stats }, ) diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 07ac96f..5ccd4cc 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -17,7 +17,9 @@ use wa_ir::{ ResponseVariantKind, }; -use crate::response_smax::{Resolved, Resolver, analyze_module_exports, scan_cascade_variants}; +use crate::response_smax::{ + Drops, Resolved, Resolver, analyze_module_exports, scan_cascade_variants, +}; use wa_transform::ModuleDefinition; /// Index of smax response parsers, keyed for request→response linkage. @@ -34,13 +36,21 @@ pub struct ResponseIndex { /// in a parser but not structurally resolvable, by reason. Surfaced under /// `manifest.diagnostics.iq.dropsByReason` so "no constraint here" and "a constraint /// we failed to extract" never look alike to a consumer. - drops: BTreeMap, + /// + /// Shared with the resolvers this index builds later, so the request-anchored + /// fallback reports into it too — and therefore only meaningful once the whole scan + /// has run. [`crate::scan_iq_with_diagnostics`] snapshots it at the end, not here. + drops: Drops, } impl ResponseIndex { - /// See [`ResponseIndex::drops`]. - pub(crate) fn drop_counts(&self) -> &BTreeMap { - &self.drops + /// See [`ResponseIndex::drops`]. Call after the scan; the fallback path adds to it. + pub(crate) fn drop_counts(&self) -> BTreeMap { + self.drops + .borrow() + .iter() + .map(|(reason, keys)| (reason.clone(), keys.len())) + .collect() } /// Look up the response for operation `x` (derived from a request module name). @@ -59,7 +69,7 @@ impl ResponseIndex { .iter() .map(|(n, s)| (n.as_str(), s.as_str())) .collect(); - let resolver = Resolver::new(&slices); + let resolver = Resolver::with_drops(&slices, self.drops.clone()); let prefix = format!("WASmaxIn{x}Response"); for (name, slice) in &self.in_slices { let Some(variant) = name.strip_prefix(&prefix) else { @@ -201,7 +211,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd ResponseIndex { by_x, in_slices, - drops: resolver.drop_counts(), + drops: resolver.drops(), } } diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 9970b13..4374442 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -90,9 +90,15 @@ pub(crate) struct Resolver<'a> { /// discriminators, and a module can be reached from several RPCs. A raw counter would /// therefore report one lost enum several times, which says nothing about how much /// data is actually missing. The unit is **distinct constraints**. - drops: RefCell>>, + drops: Drops, } +/// The shared drop collector: reason → the distinct constraints lost under it. Shared +/// (rather than owned) so a resolver built later — the request-anchored fallback in +/// [`crate::response_index`] — reports into the same place instead of dropping its +/// findings on the floor. +pub(crate) type Drops = std::rc::Rc>>>; + impl<'a> Resolver<'a> { pub(crate) fn new(slices: &'a HashMap<&'a str, &'a str>) -> Self { Resolver { @@ -102,12 +108,14 @@ impl<'a> Resolver<'a> { assert_cache: RefCell::new(HashMap::new()), assert_in_progress: RefCell::new(HashSet::new()), enum_cache: RefCell::new(HashMap::new()), - drops: RefCell::new(BTreeMap::new()), + drops: Drops::default(), } } - /// The constraints this resolver saw but could not resolve, by reason (see - /// [`Resolver::drops`]). Snapshot; safe to call after a scan. + /// The drops this resolver alone recorded, as counts. Production code reads the + /// shared collector through [`crate::response_index::ResponseIndex`] instead, since + /// the fallback resolver reports into the same place; this is the unit-test view. + #[cfg(test)] pub(crate) fn drop_counts(&self) -> BTreeMap { self.drops .borrow() @@ -116,6 +124,19 @@ impl<'a> Resolver<'a> { .collect() } + /// A resolver that reports its drops into an existing collector. + pub(crate) fn with_drops(slices: &'a HashMap<&'a str, &'a str>, drops: Drops) -> Self { + Self { + drops, + ..Self::new(slices) + } + } + + /// The shared collector, for a caller that needs to read it after further scanning. + pub(crate) fn drops(&self) -> Drops { + self.drops.clone() + } + /// Record one unresolvable constraint under `reason`, identified by `key` so the /// same loss seen again on a second analysis pass is not counted twice. fn drop_note_keyed(&self, reason: &str, key: String) { diff --git a/generated/abprops/index.json b/generated/abprops/index.json index 3bd43c4..cd26ba2 100644 --- a/generated/abprops/index.json +++ b/generated/abprops/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "configs": [ { diff --git a/generated/appstate/index.json b/generated/appstate/index.json index 6f217f2..42f57b2 100644 --- a/generated/appstate/index.json +++ b/generated/appstate/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "collections": [ "regular", diff --git a/generated/enums/index.json b/generated/enums/index.json index a315522..836f903 100644 --- a/generated/enums/index.json +++ b/generated/enums/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "enums": [ { diff --git a/generated/incoming/index.json b/generated/incoming/index.json index eab5961..3b50273 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "incoming": [ { diff --git a/generated/iq/index.json b/generated/iq/index.json index 197315c..1b6b603 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "stanzas": [ { diff --git a/generated/manifest.json b/generated/manifest.json index fc00ec6..b828ed0 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -41,37 +41,37 @@ "abprops": { "file": "abprops/index.json", "schema": "schema/abprops.schema.json", - "sha256": "63cda1b8581c73c73b74c624a273d7d8079727142023f213dc9d59dcc027c6fa" + "sha256": "aad4063a4e83ec6f7ce7f57f3e13dca9e06c7afe76e93ef97a1c3e7dc6f8df60" }, "appstate": { "file": "appstate/index.json", "schema": "schema/appstate.schema.json", - "sha256": "861c98b83183e8a430adaafd015c043010922afabb497a4b6d3f81bba270b9a5" + "sha256": "9f5ec14ac8c3a154890539bdc2f8ed2644867be77089ede6f20165ac0e01e7d2" }, "enums": { "file": "enums/index.json", "schema": "schema/enums.schema.json", - "sha256": "3c3f6eec50a86cabb3afb2ed6da234a3e289bd6849d0c2046e5f1e6f5260c3f8" + "sha256": "a8aee81f035167ab5da601bb1f8caad8b2978707ed1875a4113c1935cec8f050" }, "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "3c8baa4affd8f9f7a9ad7f96a43fad2312708a6004bee9f2383c665990096c42" + "sha256": "450282f9bc3405a8fa8307a44faabe5b3936185d762b1f7618c8f928ed183db4" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "95479b02b1d6826838a0084940de29317e60f9c774c926d066e3382023c186cc" + "sha256": "00e670a763c73e70476d36e873caf495f4d6c419020161d9253fdc0493a9323c" }, "mex": { "file": "mex/index.json", "schema": "schema/mex.schema.json", - "sha256": "5865b9e19925e6fc78ba93fc63b2f919d717dc028d216a3964993fc861d53d31" + "sha256": "c3f1927f43b5ed47ed5354a30f4caddc1aa18b702852c789a5e2c722a927232d" }, "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "58e04fb19b5c93259cbb7bf79ada3ccb5071b596f74613904a2234fa361ca4b3" + "sha256": "a2e5c6c0dd6b524e1b235d6d52e48794e0f83fd53f3cd7075475cf296fc7c7b5" }, "proto": { "file": "proto/WAProto.proto", @@ -80,27 +80,27 @@ "srvreq": { "file": "srvreq/index.json", "schema": "schema/srvreq.schema.json", - "sha256": "bd2c606ce005b84eabbc413c9211ce381b32cd6e066112dedd82058a4ef847a5" + "sha256": "d47987b5c0fb452136fee81389457a7bed5089953a079250e1d4152cfeb299c4" }, "stanza": { "file": "stanza/index.json", "schema": "schema/stanza.schema.json", - "sha256": "13a78fcf9b5e09b7cc9af26657aa95a9ba1c7035be6513238b913166ea7fcd08" + "sha256": "0245cee882b580cb75af339dd3a128c76bd3cc34960be31081593999ef02b8b7" }, "tokens": { "file": "tokens/index.json", "schema": "schema/tokens.schema.json", - "sha256": "7c1b8c84940449264b3a4966b7a61d0602ad405c85bfb6a5be38bda6b531080c" + "sha256": "d2ab9a3ebbb0a046e93fb2d01d6df6aca306042e6ad665f0b366c370865dac24" }, "wam": { "file": "wam/index.json", "schema": "schema/wam.schema.json", - "sha256": "a89414f0259598f7544302dbb6bad805c32fd62e63f67f7657ae581a99a9ebf3" + "sha256": "935227cad7e239bcf5a239cf67619b3c43584acd6cba5f516ba1c298670d6d40" }, "wasm": { "file": "wasm/index.json", "schema": "schema/wasm.schema.json", - "sha256": "fd6bab59135b2bd147074bf2bb802d7db4ea289541629758e141a91954a31d4e" + "sha256": "362157f1e1e59cecbd4e89c9eb4f2cc1465c81ab8718fddded80d8fccb3a2713" } }, "enumDefs": 326, @@ -110,7 +110,7 @@ "mexOperations": 134, "notifTypes": 27, "protoEntities": 264, - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "serverRequestDefs": 22, "stanzaDefs": 176, "tokenDoubleByte": 1024, diff --git a/generated/mex/index.json b/generated/mex/index.json index ad45e5f..0f4f0fc 100644 --- a/generated/mex/index.json +++ b/generated/mex/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "operations": { "ACSServerProviderConfig": { diff --git a/generated/notif/index.json b/generated/notif/index.json index 1ceb76a..df841c9 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "dispatcherModules": [ "WAWebCommsHandleLoggedInStanza", diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 096fb70..5ccb507 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -264,6 +264,17 @@ "description": "The element's text content is read instead of an attribute\n(`child(\"body\").contentString()`).", "type": "boolean" }, + "enumRef": { + "description": "The wire enum the value is validated against, when the arm reads it with an enum\naccessor (`child.maybeAttrEnum(\"type\", o(\"WAWebGroupApiConst\").GROUP_PARTICIPANT_TYPES)`).\nWithout it a `\"type\": \"enum\"` field says the value is constrained but not to what,\nwhich leaves a parser and an emitter guessing — the same gap `enumRef` closes on\nthe response side.\n\nAbsent when the accessor validates against an **anonymous** lookup table declared\ninline in the handler (`attrEnumOrNullIfUnknown(\"reason\", v)` where `v` is a local\nobject literal). Such a table has no stable name — the identifier is whatever the\nminifier picked this build — so naming it would churn the IR on every WhatsApp\nrollout and invent an identity the bundle does not have. Reported as unresolved\nrather than faked; recovering those value sets is a separate modelling question.", + "anyOf": [ + { + "$ref": "#/$defs/AttrEnumRef" + }, + { + "type": "null" + } + ] + }, "name": { "description": "The output field name in the parsed action (`\"duration\"`).", "type": "string" diff --git a/generated/srvreq/index.json b/generated/srvreq/index.json index 60e3dd3..d8fa41c 100644 --- a/generated/srvreq/index.json +++ b/generated/srvreq/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "requests": [ { diff --git a/generated/stanza/index.json b/generated/stanza/index.json index 140037e..4069e15 100644 --- a/generated/stanza/index.json +++ b/generated/stanza/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "stanzas": [ { diff --git a/generated/tokens/index.json b/generated/tokens/index.json index 5fbe3c6..5d1b4bd 100644 --- a/generated/tokens/index.json +++ b/generated/tokens/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "dictVersion": 3, "singleByte": [ diff --git a/generated/wam/index.json b/generated/wam/index.json index 95d28fd..6791d3e 100644 --- a/generated/wam/index.json +++ b/generated/wam/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "events": [ { diff --git a/generated/wasm/index.json b/generated/wasm/index.json index 770ec51..96d4c6c 100644 --- a/generated/wasm/index.json +++ b/generated/wasm/index.json @@ -1,5 +1,5 @@ { - "schemaVersion": "1.1.0", + "schemaVersion": "2.0.0", "waVersion": "2.3000.1043899084", "binaries": [ { From f71f94a1752097b7be629d816bd936b0b704889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 19:06:56 -0300 Subject: [PATCH 07/37] review: branch-merge value-position helpers, and stop `||` weakening a required read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five findings on 1bf091c. Three applied; two rejected on evidence. Applied: - A helper used in VALUE position (an `extends` operand, an object-property value) had all its result branches accumulated into one definition, so a helper returning `{x}` in one branch and `{y}` in another made the enclosing action require both and reject either legal payload. Branches are now folded separately and merged with `merge_action`, the same optionality rule the switch arms use. The wholesale-delegation path got this last round; this is its value-position twin. - `a || b` / `a ?? b` were classified as presence guards, so a required read with a default (`child.attrString("code") || "none"`) came out optional — `attrString` still rejects an absent attribute; the right operand only defaults a value the parser already demanded. Only a ternary and `guard && value` gate a read now. - An unresolvable `literal` on a node reached through `flattenedChildWithTag` reported nothing: the drop note sat inside the same `source_path.is_none()` guard as the root assertion, so the field carried no pin while `dropsByReason` simultaneously claimed nothing was lost. The assertion is root-relative and stays gated; the drop is not and no longer is. Rejected, with the checks: - "Include validation-only pins in error arms" — the premise does not hold in this bundle. Swept every error variant for a `code`/`text` pinned in a `UnionVariant`'s assertions but missing from its fields: zero. The cited shape is `IQErrorReportTokenValidationFail`, whose arm carries `code: 548` and no `text` — and that is the fact, not a gap: the mixin reads `attrString(e, "text")` with no `literal`, so it accepts ANY text with that code. Sourcing arms from assertions would be dead code today; the actionable part was that a code-only arm looked like a defect, so `ErrorArm::text` now documents both ways absence arises (a fallback arm pinning neither value, and a code-only arm reading the text freely). - "Bind helper parameters to call arguments" — latent, and the fix is structurally awkward. Every wholesale delegation in the bundle passes NODES (`T(chat, child, author)`, `I(child)`), which need no binding since accessors are read structurally off whatever receiver they have; a helper taking a wire VALUE appears nowhere. Seeding it would also have to pair parameter names from the helper's reparse with argument expressions from the caller's — two different allocators — for no present gain. Determinism holds, schemas validate, codegen byte-identical, no --allow-shrink. --- crates/wa-ir/src/iq.rs | 10 ++++- crates/wa-notif/src/actions.rs | 60 ++++++++++++++++++++++++--- crates/wa-notif/src/tests.rs | 44 +++++++++++++++++++- crates/wa-scan/src/response_smax.rs | 40 +++++++++++------- generated/schema/incoming.schema.json | 2 +- generated/schema/iq.schema.json | 2 +- generated/schema/notif.schema.json | 2 +- generated/schema/srvreq.schema.json | 2 +- generated/schema/stanza.schema.json | 2 +- 9 files changed, 136 insertions(+), 28 deletions(-) diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index 0efc8ab..bd5d218 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -569,9 +569,15 @@ pub struct ErrorArm { /// The exact `code` this arm pins, when it pins one. #[serde(default, skip_serializing_if = "Option::is_none")] pub code: Option, - /// The exact `text` this arm pins. Absent on a fallback arm, which accepts any text - /// within [`code_min`]..=[`code_max`]. + /// The exact `text` this arm pins, when it pins one. /// + /// Absent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins + /// neither value and accepts any text within [`code_min`]..=[`code_max`], while a + /// code-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail` + /// pins `code=548` and accepts whatever `text` the server sends with it. In both, an + /// emitter is free to choose the text; it is only [`code`] that is constrained. + /// + /// [`code`]: ErrorArm::code /// [`code_min`]: ErrorArm::code_min /// [`code_max`]: ErrorArm::code_max #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 1a21154..60170c5 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -726,8 +726,50 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) }) else { return; }; + // Each of the helper's result branches is folded on its own and then MERGED, not + // accumulated: a helper returning `{x: …}` in one branch and `{y: …}` in another + // describes two legal shapes, and combining them would make the enclosing action + // require both and reject either. `merge_action` weakens what only some branches + // carry — the same rule the switch arms use. + let mut branches: Vec = Vec::new(); for (shape, scope) in fn_result_shapes(func, &Scope::new()) { - fold_shape(shape, &scope, def, ctx, depth + 1); + let mut one = empty_action(String::new()); + fold_shape(shape, &scope, &mut one, ctx, depth + 1); + match branches + .iter_mut() + .find(|b| b.action_type == one.action_type) + { + Some(existing) => merge_action(existing, one), + None => branches.push(one), + } + } + // Distinct action types cannot arise here (a value-position helper contributes + // fields, it does not pick the action), so folding the merged branches in is safe. + for b in branches { + merge_into(def, b); + } +} + +/// Fold a helper's contribution into the enclosing action, keeping what the enclosing +/// object already stated and weakening nothing it owns. +fn merge_into(def: &mut NotifActionDef, from: NotifActionDef) { + if def.action_type.is_none() { + def.action_type = from.action_type; + } + for f in from.fields { + if !def.fields.iter().any(|x| x.name == f.name) { + def.fields.push(f); + } + } + for c in from.children { + if !def.children.iter().any(|x| x.name == c.name) { + def.children.push(c); + } + } + for c in from.constant_fields { + if !def.constant_fields.iter().any(|x| x.name == c.name) { + def.constant_fields.push(c); + } } } @@ -880,11 +922,19 @@ fn read_field<'b, 'a>( } /// Whether the value is read behind a presence guard, so the attribute may be absent. +/// +/// A ternary and a `guard && value` both gate the read on something. `a || b` / `a ?? b` +/// do NOT: the accessor on the left runs unconditionally and `attrString` still rejects +/// an absent attribute — the right operand only supplies a default for a value the +/// parser already required. Treating those as guards marked required fields optional. fn is_guarded(e: &Expression) -> bool { - matches!( - e, - Expression::ConditionalExpression(_) | Expression::LogicalExpression(_) - ) + match e { + Expression::ConditionalExpression(_) => true, + Expression::LogicalExpression(l) => { + l.operator == oxc_syntax::operator::LogicalOperator::And + } + _ => false, + } } /// The value branch of a `cond ? value : fallback` / `a || b` guard, else `e` itself. diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index ad063ce..f9ec8ba 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -608,6 +608,7 @@ __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=d; }), 1); __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ + function w(e){ if (e.hasChild("a")) return {alpha:e.attrString("alpha")}; return {beta:e.attrString("beta")}; } function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; }); } @@ -627,7 +628,7 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr var n = t.attrString("subject"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SUBJECT, subject:n, note:t.hasAttr("note")&&t.attrString("note")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EPHEMERAL: - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration")}; + return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration"), code:t.attrString("code")||"none"}, w(t)); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:0}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { @@ -924,3 +925,44 @@ fn an_action_enum_field_carries_its_variants() { let values: Vec<&str> = er.variants.iter().map(|v| v.value.as_str()).collect(); assert_eq!(values, ["admin", "superadmin", "participant"]); } + +#[test] +fn a_value_position_helper_weakens_its_branch_only_fields() { + // A helper used as an `extends` operand can return mutually exclusive shapes. + // Accumulating them makes the enclosing action require BOTH and reject either legal + // payload, so the branches are merged with the same optionality rule the switch arms + // use: a field only some branches carry is optional. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let eph = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "ephemeral") + .expect("ephemeral arm"); + let f = |name: &str| eph.fields.iter().find(|f| f.name == name); + for name in ["alpha", "beta"] { + let field = f(name).unwrap_or_else(|| panic!("{name} recovered")); + assert!(!field.required, "{name} comes from only one branch"); + } + // The arm's own unconditional read stays required. + assert!(f("duration").expect("duration").required); +} + +#[test] +fn a_defaulted_read_stays_required() { + // `child.attrString("code") || "none"` still calls `attrString`, which rejects an + // absent attribute — the right operand only defaults a value the parser already + // demanded. Classifying every logical expression as a presence guard marked it + // optional. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let code = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "ephemeral") + .expect("ephemeral arm") + .fields + .iter() + .find(|f| f.name == "code") + .expect("code field"); + assert_eq!(code.wire_name, "code"); + assert!(code.required, "`a || default` is not a presence guard"); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 4374442..f15741c 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -618,26 +618,36 @@ fn classify_call( .get(3) .and_then(arg_expr) .and_then(|e| reference_path_of(e, bindings)); - if source_path.is_none() - && let Some(attr) = attr - { + // The ASSERTION is root-relative, so it is only emitted for a pin on the + // parser's own node. The DROP is not: a nested pin we could not resolve is a + // constraint lost just the same, and suppressing its report along with the + // assertion would leave the field with no pin while `dropsByReason` + // simultaneously claimed nothing had gone missing. + if let Some(attr) = attr { match (&literal_value, &reference_path) { - (Some(value), _) => assertions.push(ResponseAssertion { - kind: AssertionKind::Attr, - name: Some(attr.to_string()), - value: Some(value.clone()), - reference_path: None, - }), - (None, Some(path)) => assertions.push(ResponseAssertion { - kind: AssertionKind::Reference, - name: Some(attr.to_string()), - value: None, - reference_path: Some(path.clone()), - }), + (Some(value), _) if source_path.is_none() => { + assertions.push(ResponseAssertion { + kind: AssertionKind::Attr, + name: Some(attr.to_string()), + value: Some(value.clone()), + reference_path: None, + }) + } + (None, Some(path)) if source_path.is_none() => { + assertions.push(ResponseAssertion { + kind: AssertionKind::Reference, + name: Some(attr.to_string()), + value: None, + reference_path: Some(path.clone()), + }) + } (None, None) => resolver.drop_note_keyed( "literal attr value not statically resolvable", attr.to_string(), ), + // A resolved pin on a descended node: carried on the field below, + // where it belongs, rather than as a root assertion. + _ => {} } } let inner = args diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index 71fff7b..56c8a58 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -135,7 +135,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", "type": [ "string", "null" diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index b7bf340..eb589d8 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -140,7 +140,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", "type": [ "string", "null" diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 5ccb507..1c32e4f 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -151,7 +151,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", "type": [ "string", "null" diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 0007106..0af0b6c 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -135,7 +135,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", "type": [ "string", "null" diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index 593b943..ed236a9 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -142,7 +142,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins. Absent on a fallback arm, which accepts any text\nwithin [`code_min`]..=[`code_max`].\n\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", "type": [ "string", "null" From 3552d0d3d490809692899a426e3195b4b1385fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 19:29:40 -0300 Subject: [PATCH 08/37] review: classify wire errors by the root discriminator, merge mapped-child branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings on f71f94a. All applied, and one of them caught a mistake I made two rounds earlier. `SetResponsePreKeySuccessVnameFailure` was classified a SUCCESS because `variant_kind` reads only the tag's name tokens, and every token in that name says success. It asserts `type="error"` and parses an `` wrapping an inner ``: the prekeys landed, the vname did not, and the server said so with an error stanza. Because the error vocabulary is gated on the kind, the codes and texts it accepts were being dropped. The classification now takes the parser's root discriminator first and falls back to the tag tokens, so the protocol decides rather than a naming convention. That also corrects round 2 of this review: I stripped the vocabulary from three "success" variants as bogus, and for two of them (`FetchKeyBundlesResponseSuccess`, `FetchMissingPreKeysResponseSuccess`, both asserting `type="result"` with per-item nested errors) that was right, but for this one it was not. Exactly one variant reclassifies, and it goes from an empty vocabulary to `[207]` / three texts / range 400–599. Its envelope code needed one more thing: a two-level error has pins no ARM can carry, since an arm describes one innermost alternative and the outer `code="207"` belongs to none of them. The variant's own pins are now folded into the flat vocabulary so the envelope is not simply lost, while `errorArms` stays the per-alternative view. Also: - A `mapChildrenWithTag` callback returning different shapes from an `if` had every property in its body flattened into one child definition, marking a field read in only one branch as required. Callbacks are now read per return shape and merged — the third and last place that rule was missing. - A constant table assigned to a non-export receiver (`cache.GROUP_ACTIONS = local` before the real export) could be taken as the export and, being first, kept — resolving case labels through an unrelated table. Only the module factory's own parameters count as receivers now, falling back to the old rule when the factory shape is unrecognised, so a narrower rule cannot turn an unfamiliar module into a silently empty one. The mapped-child change initially cost 49 shape elements — the floor guard caught it, which is what it is for. Two causes, both the minifier's doing: the object is built into a local and returned by name, and the return is a comma expression (`return sideEffect(), u`) whose value is its last element. Both are handled, and coverage is back to 181 with no `--allow-shrink`. Round-trip coverage moves 141 -> 140 shapes: the reclassified variant is no longer a success shape, so the guard no longer walks it. Determinism holds, schemas validate, codegen byte-identical. --- crates/wa-notif/src/actions.rs | 151 ++++++++++++++++++++++----- crates/wa-notif/src/tests.rs | 51 ++++++++- crates/wa-scan/src/response_index.rs | 39 +++++-- generated/iq/index.json | 29 ++++- generated/manifest.json | 4 +- 5 files changed, 238 insertions(+), 36 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 60170c5..bfb332b 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -121,6 +121,14 @@ fn const_string_map(slice: &str, export: &str) -> Option { return None; } let mut collector = ConstCollector { + // Only the module factory's own parameters count as export receivers. Without + // that, `cache.GROUP_ACTIONS = local` written before the real + // `exports.GROUP_ACTIONS = actual` would be taken as the export and — being + // first — kept, resolving case labels through an unrelated table and minting + // wrong wire tags. Empty when the factory shape is not recognised, in which case + // any identifier receiver is accepted, as before: a narrower rule must not turn + // an unfamiliar module into a silently empty one. + receivers: factory_params(&ret.program), locals: HashMap::new(), exports: HashMap::new(), }; @@ -139,7 +147,40 @@ enum Export { Local(String), } +/// The parameter names of the `__d(name, deps, factory)` module factory — the only +/// identifiers a module can legitimately hang an export off. +fn factory_params(program: &oxc_ast::ast::Program) -> Vec { + fn params_of(e: &Expression) -> Option> { + let f = match e { + Expression::ParenthesizedExpression(p) => return params_of(&p.expression), + Expression::FunctionExpression(f) => f, + _ => return None, + }; + Some( + f.params + .items + .iter() + .filter_map(|p| p.pattern.get_identifier_name().map(|n| n.to_string())) + .collect(), + ) + } + for stmt in &program.body { + if let Statement::ExpressionStatement(es) = stmt + && let Some(call) = as_call(&es.expression) + { + for arg in &call.arguments { + if let Some(params) = arg_expr(arg).and_then(params_of) { + return params; + } + } + } + } + Vec::new() +} + struct ConstCollector { + /// See the note at the construction site. + receivers: Vec, /// Local name → its all-string object, or `None` when the same minified name is /// bound to **different** objects in the module. The minifier reuses short /// identifiers aggressively across nested scopes, so a module-wide "last one wins" @@ -173,6 +214,9 @@ impl<'a> Visit<'a> for ConstCollector { fn visit_assignment_expression(&mut self, a: &oxc_ast::ast::AssignmentExpression<'a>) { if let Some(m) = a.left.as_member_expression() && let Some(prop) = m.static_property_name() + && m.object().get_identifier_reference().is_some_and(|id| { + self.receivers.is_empty() || self.receivers.iter().any(|r| r == id.name.as_str()) + }) { if let Some(map) = string_const_object(&a.right) { self.exports @@ -495,6 +539,15 @@ fn collect_branches<'b, 'a>(e: &'b Expression<'a>, out: &mut Vec<&'b Expression< collect_branches(&c.alternate, out); } Expression::ParenthesizedExpression(p) => collect_branches(&p.expression, out), + // `return sideEffect(…), value` — a comma expression's value is its LAST element. + // The minifier uses it constantly to fold a call in before returning the object + // (`return c || C(chat, u, tag), u`), so taking the whole sequence as the shape + // finds no object at all. + Expression::SequenceExpression(seq) => { + if let Some(last) = seq.expressions.last() { + collect_branches(last, out); + } + } other => out.push(other), } } @@ -1102,42 +1155,34 @@ fn collect_accessor_fields<'b, 'a>( if let Some(stmts) = function_body_of(e) { scope.extend(scope_bindings(stmts)); } - struct Walker<'o, 'b, 'a> { - scope: &'o Scope<'b, 'a>, - consts: &'o ConstResolver<'o>, - out: &'o mut Vec, - } - impl<'b, 'a> Visit<'a> for Walker<'_, 'b, 'a> { - fn visit_object_property(&mut self, p: &oxc_ast::ast::ObjectProperty<'a>) { - if let Some(key) = wa_oxc::property_key_name(&p.key) - && let Some(field) = read_field(key, &p.value, self.scope, self.consts) - && !self.out.iter().any(|f| f.name == field.name) - { - self.out.push(field); - } - walk::walk_object_property(self, p); - } - } // Only function arguments hold the per-element shape; a bare expression contributes // nothing (the tag string, the bounds). - if matches!( + if !matches!( e, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) ) { - let mut w = Walker { - scope: &scope, - consts, - out, - }; - w.visit_expression(e); + return; + } + // Per RETURN SHAPE, then merged — the same rule the action arms and the value-position + // helpers use. A callback that returns different objects from an `if` or a ternary + // describes alternative element shapes: flattening every property in the body into + // one list would mark a field read in only one branch as required, and let two + // branches reusing an output name silently keep whichever came first. + let mut merged: Vec = Vec::new(); + let mut branches = 0usize; + for (shape, inner) in fn_result_shapes(e, &scope) { + branches += 1; + let mut fields = Vec::new(); + collect_shape_fields(shape, &inner, consts, &mut fields); + merge_fields(&mut merged, fields, branches == 1); } // A callback that returns a bare value rather than an object // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the // field by, so the wire attribute names it — better than reporting no fields at all. - if out.is_empty() { + if merged.is_empty() { for (shape, inner) in fn_result_shapes(e, &scope) { if let Some(acc) = find_accessor(shape, &inner) { - out.push(NotifActionField { + merged.push(NotifActionField { name: acc.wire_name.clone(), wire_name: acc.wire_name, field_type: wap::method_field_type(&acc.method), @@ -1148,4 +1193,60 @@ fn collect_accessor_fields<'b, 'a>( } } } + for f in merged { + if !out.iter().any(|x| x.name == f.name) { + out.push(f); + } + } +} + +/// Every `{ key: }` property reachable in one result shape. +fn collect_shape_fields<'b, 'a>( + shape: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + consts: &ConstResolver, + out: &mut Vec, +) { + struct Walker<'o, 'b, 'a> { + scope: &'o Scope<'b, 'a>, + consts: &'o ConstResolver<'o>, + out: &'o mut Vec, + } + impl<'b, 'a> Visit<'a> for Walker<'_, 'b, 'a> { + fn visit_object_property(&mut self, p: &oxc_ast::ast::ObjectProperty<'a>) { + if let Some(key) = wa_oxc::property_key_name(&p.key) + && let Some(field) = read_field(key, &p.value, self.scope, self.consts) + && !self.out.iter().any(|f| f.name == field.name) + { + self.out.push(field); + } + walk::walk_object_property(self, p); + } + } + let mut w = Walker { scope, consts, out }; + // The minifier almost always builds the object into a local and returns the name + // (`var u = {…}; return sideEffect(), u`), so the returned expression is an + // identifier — walking it directly would find no properties at all. + w.visit_expression(deref_ident(shape, scope)); +} + +/// Fold one branch's fields into the accumulated set: a field either branch reads is +/// present, and required only when EVERY branch reads it unconditionally. +fn merge_fields(into: &mut Vec, from: Vec, first: bool) { + if !first { + for existing in into.iter_mut() { + if !from.iter().any(|f| f.name == existing.name) { + existing.required = false; + } + } + } + for f in from { + match into.iter_mut().find(|x| x.name == f.name) { + Some(existing) => existing.required &= f.required, + None => into.push(NotifActionField { + required: f.required && first, + ..f + }), + } + } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index f9ec8ba..2b7841b 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -596,7 +596,9 @@ __d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o, l.handleDevicesNotification = h; }), 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); + var cache={}; + cache.GROUP_NOTIFICATION_TAG={ADD:"WRONG_add",SUBJECT:"WRONG_subject"}; + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ @@ -604,11 +606,15 @@ __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ l.GROUP_PARTICIPANT_TYPES=g; }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ - var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); + var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",MODIFY:"modify",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); l.GROUP_ACTIONS=d; }), 1); __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ function w(e){ if (e.hasChild("a")) return {alpha:e.attrString("alpha")}; return {beta:e.attrString("beta")}; } + function q(e){ return e.mapChildrenWithTag("entry", function(x){ + if (x.hasChild("full")) return {id:x.attrString("id"), extra:x.attrString("extra")}; + return {id:x.attrString("id")}; + }); } function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; }); } @@ -631,6 +637,8 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration"), code:t.attrString("code")||"none"}, w(t)); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:0}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MODIFY: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.MODIFY, entries:q(t)}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { var n; return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0}; @@ -966,3 +974,42 @@ fn a_defaulted_read_stays_required() { assert_eq!(code.wire_name, "code"); assert!(code.required, "`a || default` is not a presence guard"); } + +#[test] +fn a_const_table_on_a_non_export_receiver_is_ignored() { + // `cache.GROUP_NOTIFICATION_TAG = {…}` written before the real + // `exports.GROUP_NOTIFICATION_TAG = …` would otherwise be taken as the export and, + // being first, kept — resolving case labels through an unrelated table and minting + // wrong wire tags. Only the module factory's own parameters count as receivers. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let tags: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .map(|a| a.wire_tag.as_str()) + .collect(); + assert!(tags.contains(&"add"), "the real table must win: {tags:?}"); + assert!( + !tags.iter().any(|t| t.starts_with("WRONG_")), + "decoy table leaked into the catalog: {tags:?}" + ); +} + +#[test] +fn a_branching_mapped_child_callback_weakens_its_branch_only_fields() { + // A callback returning different objects from an `if` describes alternative element + // shapes. Flattening every property in the body into one list marks a field read in + // only one branch as required, so a consumer would reject a legal element. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let entries = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "modify") + .expect("modify arm") + .children + .iter() + .find(|c| c.name == "entries") + .expect("entries child"); + let f = |name: &str| entries.fields.iter().find(|f| f.name == name); + assert!(f("id").expect("id").required, "read by every branch"); + assert!(!f("extra").expect("extra").required, "read by one branch"); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 5ccd4cc..c8e6516 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -78,7 +78,7 @@ impl ResponseIndex { // Skip the bare `…Response`, error variants, and mixin payloads. if variant.is_empty() || variant.contains("Mixin") - || variant_kind(variant) != ResponseVariantKind::Success + || variant_kind(variant, &[]) != ResponseVariantKind::Success { continue; } @@ -136,7 +136,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd Some(Resolved::Union(v)) if !v.is_empty() => vec![union_field("value", v)], _ => Vec::new(), }; - let kind = variant_kind(&tag); + let kind = variant_kind(&tag, &resolver.assertions(&module, &func)); if kind == ResponseVariantKind::Success && primary.is_empty() { primary = fields.clone(); } @@ -276,8 +276,14 @@ impl ErrorVocabulary { fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut v = ErrorVocabulary::default(); collect_error_arms(fields, &mut v.arms); - // The flat lists are a VIEW over the arms, so the two can never disagree. - for a in &v.arms { + // A two-level error — an outer `` wrapping an inner `` whose + // text the disjunction pins — has pins the ARMS cannot carry: an arm describes one + // innermost alternative, so the envelope's own code belongs to none of them. + // `SetResponsePreKeySuccessVnameFailure` is that shape. Fold the variant's own pins + // into the flat vocabulary so the envelope's code is not simply lost, while the arms + // stay the per-alternative view. + let envelope = arm_pins(fields); + for a in v.arms.iter().chain(std::iter::once(&envelope)) { v.codes.extend(a.code); v.texts.extend(a.text.clone()); if let (Some(min), Some(max)) = (a.code_min, a.code_max) { @@ -395,8 +401,29 @@ fn rpc_op_name(module: &str) -> String { .to_string() } -/// Classify a response variant by its discriminator tag's tokens. -fn variant_kind(tag: &str) -> ResponseVariantKind { +/// Classify a response variant: by what its parser asserts on the wire first, then by +/// its discriminator tag's tokens. +/// +/// The root discriminator wins because it is the protocol talking rather than a naming +/// convention. `SetResponsePreKeySuccessVnameFailure` reads as a success by every token +/// in its name, yet it asserts `type="error"` and parses an `` — the +/// prekeys landed, the vname did not, and the server said so with an error stanza. +/// Judging it by the tag alone classified a wire error as a success and, since the error +/// vocabulary is gated on the kind, silently dropped the codes and texts it accepts. +fn variant_kind(tag: &str, assertions: &[wa_ir::ResponseAssertion]) -> ResponseVariantKind { + let asserts_error = assertions.iter().any(|a| { + a.kind == wa_ir::AssertionKind::Attr + && a.name.as_deref() == Some("type") + && a.value.as_deref() == Some("error") + }); + if asserts_error { + return ResponseVariantKind::Error; + } + variant_kind_by_tag(tag) +} + +/// The name-token fallback, for a variant whose parser pins no `type`. +fn variant_kind_by_tag(tag: &str) -> ResponseVariantKind { const ERROR_TOKENS: &[&str] = &[ "Error", "InvalidRequest", diff --git a/generated/iq/index.json b/generated/iq/index.json index 1b6b603..7d4f3e7 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -8385,7 +8385,34 @@ { "tag": "SetResponsePreKeySuccessVnameFailure", "moduleName": "WASmaxInPreKeysSetResponsePreKeySuccessVnameFailure", - "kind": "success", + "kind": "error", + "errorArms": [ + { + "name": "SetVnameFailureNotAcceptable", + "text": "not-acceptable" + }, + { + "name": "SetVnameFailureBadRequest", + "text": "bad-request" + }, + { + "name": "SetVnameFailureInternalServerError", + "text": "internal-server-error" + }, + { + "name": "SetVnameFailureFallback" + } + ], + "errorCodes": [ + 207 + ], + "errorTexts": [ + "bad-request", + "internal-server-error", + "not-acceptable" + ], + "errorCodeMin": 400, + "errorCodeMax": 599, "assertions": [ { "kind": "tag", diff --git a/generated/manifest.json b/generated/manifest.json index b828ed0..7a9423f 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,7 +5,7 @@ "iq": { "candidateModules": 207, "constraints": { - "errorArms": 639, + "errorArms": 643, "errorTexts": 38, "fieldEnumRefs": 146, "fieldLiterals": 1782, @@ -61,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "00e670a763c73e70476d36e873caf495f4d6c419020161d9253fdc0493a9323c" + "sha256": "5783eece985d5d233bd6cf86aca221e8a9a6d785953c4b7a1892031cb0b48256" }, "mex": { "file": "mex/index.json", From f529bd7498eb20d87239373dd2b6e383484b6be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 19:53:58 -0300 Subject: [PATCH 09/37] review: drop phantom actions, model the two-level error, and stop overclaiming the guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five findings on 3552d0d — two from CodeRabbit, three from Codex. All applied. Two changed committed output. `account_sync` was advertising 11 phantom actions. Its handler has a const-keyed child switch whose arms only cause side effects, so every case became an empty `NotifActionDef` with no action type, field, constant or child — entries a consumer can do nothing with, already described by the notification's `content` and sub-discriminants, and inflating the count. A switch is now only taken as a payload action union when at least one arm says something; an individual empty arm is still kept, since knowing a tag is dispatched beats omitting it (`w:gp2`'s `missing_participant_identification` is that case). 58 -> 47 actions, all 11 removed ones verifiably empty, nothing else in the domain changed. The two-level error contradicted its own documentation. Last round I folded the envelope's pins into the flat vocabulary and left `errorArms` described as authoritative with an explicit "do not combine the two" — which left no arm of `SetResponsePreKeySuccessVnameFailure` carrying enough to build a response the parser accepts. `ResponseVariant` now has `errorEnvelope`: the pins on the `` node ENCLOSING the ones the arms discriminate, recorded once rather than duplicated into every arm, so "arm + envelope" is complete. Set only when the arms came from a disjunction — without one those same pins ARE the single arm, and recording them twice would invent an envelope the response does not have. Exactly one variant has it. Enum accessors now decode to `Enum`. `method_field_type` mapped `attrEnum`/`maybeAttrEnum`/`attrEnumValues` to `String`, so the IR carried one concept as two types across domains — 486 fields said `enum`, 28 said `string` — and the new `enumRef` hung off fields a consumer filtering on `type == "enum"` would never reach, contradicting its own doc. The doc was right; the mapping was wrong. Also: `ConstResolver::enum_ref` memoizes like `member` already did, instead of re-parsing the whole module slice per enum field. The round-trip guard was overclaiming, and that is worth stating plainly because I described it as an extraction-regression guard in the PR. Emitter and checker read the same recorded field list, so a pin the extractor never wrote is invisible to both — they agree on the unconstrained shape and the test passes. It proves the recorded constraints are mutually satisfiable and correctly placed, not that none is missing. The module doc now says so, and names where omissions ARE caught: the manifest floor, plus new canaries asserting by name that the specific rules this domain exists for survive — the promote `type="admin"`, both blocklist `matched` pins, and a floor on the `from` -> request `to` echo. An aggregate can stay healthy while one variant quietly loses one pin; a named assertion cannot. Determinism holds, schemas validate, codegen byte-identical. One --allow-shrink, for the phantom removal. --- crates/wa-ir/src/iq.rs | 27 +++++++-- crates/wa-ir/src/wap.rs | 17 ++++-- crates/wa-notif/src/actions.rs | 35 ++++++++++- crates/wa-notif/src/tests.rs | 4 +- crates/wa-scan/src/response_index.rs | 22 +++++-- crates/wa-scan/tests/iq_roundtrip.rs | 87 ++++++++++++++++++++++++++- generated/incoming/index.json | 8 +-- generated/iq/index.json | 47 ++++++++------- generated/manifest.json | 8 +-- generated/notif/index.json | 43 ++----------- generated/schema/incoming.schema.json | 15 ++++- generated/schema/iq.schema.json | 15 ++++- generated/schema/notif.schema.json | 15 ++++- generated/schema/srvreq.schema.json | 15 ++++- generated/schema/stanza.schema.json | 15 ++++- 15 files changed, 277 insertions(+), 96 deletions(-) diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index bd5d218..95b0756 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -607,10 +607,27 @@ pub struct ResponseVariant { #[serde(default, skip_serializing_if = "Option::is_none")] pub error_class: Option, /// The accepted `` shapes, in parser order — **which code goes with which - /// text**. This is the authoritative form; see [`ErrorArm`] for why the flattened - /// lists below cannot express it. + /// text**. See [`ErrorArm`] for why the flattened lists below cannot express it. + /// + /// An arm describes the *discriminating* `` node. When the variant also pins + /// the node above it, those pins are in [`error_envelope`], and the full shape is + /// **arm + envelope** — an arm alone is not enough to build a response for such a + /// variant. + /// + /// [`error_envelope`]: ResponseVariant::error_envelope #[serde(default, skip_serializing_if = "Vec::is_empty")] pub error_arms: Vec, + /// Pins on the `` node **enclosing** the one the arms discriminate, for a + /// two-level error. + /// + /// `SetResponsePreKeySuccessVnameFailure` is the shape: the response carries + /// `` — the partial-failure envelope, shared by every arm — around + /// an inner `` whose text the disjunction pins. The envelope belongs to no + /// single arm, so duplicating it into all of them would misreport where the code + /// sits; it is recorded once here instead. Absent for the ordinary one-level error, + /// which is nearly all of them. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_envelope: Option, /// The **closed set** of `` values this variant accepts, ascending — the /// union over [`error_arms`], for the "does this RPC accept code N?" question. /// @@ -620,8 +637,10 @@ pub struct ResponseVariant { /// outside this set matches no branch, so the client reports a parse failure rather /// than the error. Empty when the variant pins no exact code (see [`error_code_min`]). /// - /// An emitter must pick a **pair** from [`error_arms`], never one value from this - /// list and another from [`error_texts`]. + /// An emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when + /// present), never one value from this list and another from [`error_texts`]. + /// + /// [`error_envelope`]: ResponseVariant::error_envelope /// /// [`error_arms`]: ResponseVariant::error_arms /// [`error_texts`]: ResponseVariant::error_texts diff --git a/crates/wa-ir/src/wap.rs b/crates/wa-ir/src/wap.rs index f6ea9b4..44e81e9 100644 --- a/crates/wa-ir/src/wap.rs +++ b/crates/wa-ir/src/wap.rs @@ -107,9 +107,13 @@ pub fn is_optional_method(m: &str) -> bool { /// The scalar [`ParsedFieldType`] a response accessor decodes to. pub fn method_field_type(m: &str) -> ParsedFieldType { match m { - ATTR_STRING | MAYBE_ATTR_STRING | ATTR_ENUM | MAYBE_ATTR_ENUM | ATTR_ENUM_VALUES => { - ParsedFieldType::String - } + ATTR_STRING | MAYBE_ATTR_STRING => ParsedFieldType::String, + // An enum accessor decodes to an enum, not a bare string. The smax side already + // types these `Enum` through its own normalizer, so mapping them to `String` here + // made one concept two types across domains — 486 fields said `enum` and 28 said + // `string` for the same accessors — and left an `enumRef` hanging off a field a + // consumer filtering on `type == "enum"` would never look at. + ATTR_ENUM | MAYBE_ATTR_ENUM | ATTR_ENUM_VALUES => ParsedFieldType::Enum, ATTR_INT | MAYBE_ATTR_INT | ATTR_TIME | MAYBE_ATTR_TIME => ParsedFieldType::Integer, // Each JID accessor pins the flavor (server/format) it validates. Keeping the // flavor — rather than collapsing to a bare `Jid` — is protocol-safety-critical: @@ -214,7 +218,10 @@ mod tests { fn optional_and_field_types() { assert!(is_optional_method(MAYBE_ATTR_ENUM)); assert!(!is_optional_method(ATTR_ENUM)); - assert_eq!(method_field_type(MAYBE_ATTR_ENUM), ParsedFieldType::String); + // An enum accessor decodes to an enum, matching how the smax normalizer already + // types the same concept — the two used to disagree across domains. + assert_eq!(method_field_type(MAYBE_ATTR_ENUM), ParsedFieldType::Enum); + assert_eq!(method_field_type(ATTR_ENUM), ParsedFieldType::Enum); assert_eq!(method_field_type(ATTR_INT), ParsedFieldType::Integer); assert_eq!(method_field_type(CONTENT_BYTES), ParsedFieldType::Bytes); assert_eq!( @@ -304,7 +311,7 @@ mod tests { ] { assert!(method_field_type(m).is_jid(), "{m}"); } - assert_eq!(method_field_type(ATTR_ENUM_VALUES), ParsedFieldType::String); + assert_eq!(method_field_type(ATTR_ENUM_VALUES), ParsedFieldType::Enum); // `maybe*` variants stay optional. assert!(is_optional_method(MAYBE_ATTR_TIME)); assert!(is_optional_method(MAYBE_ATTR_USER_JID)); diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index bfb332b..a8edc3c 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -45,6 +45,11 @@ type ConstMap = HashMap; pub(crate) struct ConstResolver<'a> { slices: &'a HashMap<&'a str, &'a str>, cache: std::cell::RefCell>>, + /// `(module, enum)` → its resolved variants. Memoized for the same reason `cache` is: + /// `resolve_named_enum` re-parses the whole module slice, and the resolver is hit once + /// per enum-accessor field — including a second time by the bare-value fallback, which + /// re-walks the same shapes. + enums: std::cell::RefCell>>, } impl<'a> ConstResolver<'a> { @@ -52,6 +57,7 @@ impl<'a> ConstResolver<'a> { Self { slices, cache: std::cell::RefCell::new(HashMap::new()), + enums: std::cell::RefCell::new(HashMap::new()), } } @@ -77,6 +83,16 @@ impl<'a> ConstResolver<'a> { fn enum_ref(&self, e: &Expression) -> Option { let (obj, name) = as_member(e)?; let module = as_string_lit(arg_expr(as_call(obj)?.arguments.first()?)?)?; + let key = (module.to_string(), name.to_string()); + if let Some(hit) = self.enums.borrow().get(&key) { + return hit.clone(); + } + let resolved = self.resolve_enum_uncached(module, name); + self.enums.borrow_mut().insert(key, resolved.clone()); + resolved + } + + fn resolve_enum_uncached(&self, module: &str, name: &str) -> Option { let def = wa_enums::resolve_named_enum(self.slices.get(module)?, module, name)?; let variants: Vec = def .variants @@ -353,13 +369,30 @@ struct SwitchFinder<'c, 'a> { impl<'a> Visit<'a> for SwitchFinder<'_, 'a> { fn visit_switch_statement(&mut self, switch: &SwitchStatement<'a>) { let actions = extract_switch(switch, self.ctx); - if actions.len() > self.best.as_ref().map_or(0, Vec::len) { + // A const-keyed switch whose arms only cause side effects is not a payload action + // union — it is an ordinary child dispatch, already described by the + // notification's `content` and sub-discriminants. `account_sync` has one, and + // cataloguing it minted 11 phantom actions with no action type, field, constant + // or child, inflating the count with entries a consumer can do nothing with. An + // individual empty arm is still kept (knowing a tag is dispatched beats omitting + // it); a switch where EVERY arm is empty is rejected whole. + if actions.iter().any(is_meaningful) + && actions.len() > self.best.as_ref().map_or(0, Vec::len) + { self.best = Some(actions); } walk::walk_switch_statement(self, switch); } } +/// Whether an arm says anything beyond "this tag is dispatched". +fn is_meaningful(a: &NotifActionDef) -> bool { + a.action_type.is_some() + || !a.fields.is_empty() + || !a.constant_fields.is_empty() + || !a.children.is_empty() +} + /// Read every arm of a const-keyed child-tag switch into [`NotifActionDef`]s. fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec { let mut out = Vec::new(); diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 2b7841b..f879b5a 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -927,7 +927,9 @@ fn an_action_enum_field_carries_its_variants() { .iter() .find(|f| f.name == "kind") .expect("kind field"); - assert_eq!(kind.field_type, wa_ir::ParsedFieldType::String); + // The accessor is an enum accessor, so the field is typed as one — the `enumRef` + // hangs off a field a consumer filtering on `type == "enum"` will actually reach. + assert_eq!(kind.field_type, wa_ir::ParsedFieldType::Enum); let er = kind.enum_ref.as_ref().expect("resolved enum"); assert_eq!(er.name, "GROUP_PARTICIPANT_TYPES"); let values: Vec<&str> = er.variants.iter().map(|v| v.value.as_str()).collect(); diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index c8e6516..f54cf5c 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -164,6 +164,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd kind, error_class: vocab.class(), error_arms: vocab.arms, + error_envelope: vocab.envelope, error_codes: vocab.codes, error_texts: vocab.texts, error_code_min: vocab.code_min, @@ -221,6 +222,9 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd struct ErrorVocabulary { /// The accepted `(code, text)` shapes, in parser order — the authoritative form. arms: Vec, + /// Pins on the `` node enclosing the ones the arms discriminate, for a + /// two-level error. Recorded once rather than duplicated into every arm. + envelope: Option, /// Flattened unions over `arms`, kept for the "does this RPC accept N?" question. codes: Vec, texts: Vec, @@ -275,14 +279,19 @@ impl ErrorVocabulary { /// `int_min`+`int_max`), so this reads them back rather than re-parsing the bundle. fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut v = ErrorVocabulary::default(); - collect_error_arms(fields, &mut v.arms); + let from_union = collect_error_arms(fields, &mut v.arms); // A two-level error — an outer `` wrapping an inner `` whose // text the disjunction pins — has pins the ARMS cannot carry: an arm describes one - // innermost alternative, so the envelope's own code belongs to none of them. - // `SetResponsePreKeySuccessVnameFailure` is that shape. Fold the variant's own pins - // into the flat vocabulary so the envelope's code is not simply lost, while the arms - // stay the per-alternative view. + // discriminating node, and the envelope belongs to none of them. Recorded once, so an + // "arm + envelope" reading is complete; duplicating it into every arm would misreport + // where the code sits. + // Only when the arms came from a DISJUNCTION are the variant's own pins a separate, + // enclosing node. Without a disjunction those same pins ARE the single arm, and + // recording them twice would invent an envelope the response does not have. let envelope = arm_pins(fields); + if from_union && envelope != ErrorArm::default() { + v.envelope = Some(envelope.clone()); + } for a in v.arms.iter().chain(std::iter::once(&envelope)) { v.codes.extend(a.code); v.texts.extend(a.text.clone()); @@ -305,7 +314,7 @@ fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { /// into two independent lists would let an emitter combine one arm's code with another's /// text — `400 feature-not-implemented` matches no branch and is unparseable, which is /// the whole failure class this domain exists to prevent. -fn collect_error_arms(fields: &[ParsedField], out: &mut Vec) { +fn collect_error_arms(fields: &[ParsedField], out: &mut Vec) -> bool { let mut found_union = false; collect_union_arms(fields, out, &mut found_union); if !found_union { @@ -315,6 +324,7 @@ fn collect_error_arms(fields: &[ParsedField], out: &mut Vec) { out.push(arm); } } + found_union } /// Walk the tree for disjunction alternatives, pushing one arm per leaf alternative. diff --git a/crates/wa-scan/tests/iq_roundtrip.rs b/crates/wa-scan/tests/iq_roundtrip.rs index 7ef3b65..3dcbcd3 100644 --- a/crates/wa-scan/tests/iq_roundtrip.rs +++ b/crates/wa-scan/tests/iq_roundtrip.rs @@ -19,7 +19,19 @@ //! //! Both halves are deliberately naive: the emitter only knows how to satisfy //! constraints, and the checker only knows how to verify them. Neither shares code with -//! the extractor, so a mis-extraction cannot cancel itself out. +//! the extractor, so a mis-extracted constraint cannot cancel itself out. +//! +//! # What this does NOT catch +//! +//! An **omitted** constraint. Emitter and checker read the same recorded field list, so a +//! pin the extractor never wrote is invisible to both: they agree on the unconstrained +//! shape and the round trip passes. This guard proves the recorded constraints are +//! mutually satisfiable and correctly placed — not that none is missing. +//! +//! Omissions are caught elsewhere, by two mechanisms that do have an outside reference: +//! `manifest.diagnostics.iq.constraints`, floored against the previously committed run, +//! and the named canaries at the bottom of this file, which assert that the specific +//! rules this domain exists for are still present by name. use std::collections::BTreeMap; use std::path::Path; @@ -489,3 +501,76 @@ fn a_pin_on_a_presence_flag_is_never_required() { let broken = violations(&wrong, &assertions, &fields); assert_eq!(broken.len(), 1, "{broken:?}"); } + +/// The response shape and the constraint each of the motivating bugs needs. +/// +/// The aggregate floor catches a broad collapse; it cannot catch one variant quietly +/// losing one pin, because the remaining thousands keep the totals healthy. These name +/// the exact constraints whose absence caused a real failure, so losing any one of them +/// fails loudly instead of arithmetically. +const CANARIES: &[(&str, &str, &str)] = &[ + // (variant tag, wire attribute, expected pin) — the successful promote that was + // answered ``. + ("PromoteDemoteResponseSuccessPromote", "type", "admin"), + // The blocklist update answered with a bare ``. + ("UpdateBlockListResponseSuccessWithMatch", "matched", "true"), + ( + "UpdateBlockListResponseSuccessWithMismatch", + "matched", + "false", + ), +]; + +#[test] +fn the_motivating_constraints_are_still_recorded_by_name() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../generated/iq/index.json"); + if !path.exists() { + assert!( + std::env::var_os("CI").is_none(), + "{} absent under CI", + path.display() + ); + return; + } + let ir: IqIr = serde_json::from_str(&std::fs::read_to_string(&path).expect("read")) + .expect("parse the committed IQ IR"); + + let variants: Vec<_> = ir + .stanzas + .iter() + .flat_map(|s| &s.response.variants) + .collect(); + for (tag, wire, expected) in CANARIES { + let v = variants + .iter() + .find(|v| &v.tag == tag) + .unwrap_or_else(|| panic!("variant {tag} vanished from the IR")); + let mut found = None; + walk_pinned(&v.fields, &[], &mut |f, _| { + if f.wire_name.as_deref() == Some(*wire) { + found = f.literal_value.clone(); + } + }); + assert_eq!( + found.as_deref(), + Some(*expected), + "{tag}: the pin on `{wire}` is gone — an emitter would be free to send anything there" + ); + } + + // The echo rule, on the variant that made it visible: an error answering a request + // addressed to `g.us` must put that JID in `from`. + let echoes: usize = variants + .iter() + .flat_map(|v| &v.assertions) + .filter(|a| { + a.kind == AssertionKind::Reference + && a.name.as_deref() == Some("from") + && a.reference_path.as_deref() == Some(&["to".to_string()][..]) + }) + .count(); + assert!( + echoes > 100, + "only {echoes} `from` -> request `to` echoes recorded; the rule is collapsing" + ); +} diff --git a/generated/incoming/index.json b/generated/incoming/index.json index 3b50273..e1e6e48 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -25,7 +25,7 @@ { "method": "attrEnumValues", "name": "type", - "type": "string", + "type": "enum", "required": true }, { @@ -106,7 +106,7 @@ { "method": "attrEnum", "name": "type", - "type": "string", + "type": "enum", "required": true }, { @@ -296,7 +296,7 @@ { "method": "maybeAttrEnum", "name": "type", - "type": "string", + "type": "enum", "required": false }, { @@ -2560,7 +2560,7 @@ { "method": "attrEnum", "name": "readreceipts", - "type": "string", + "type": "enum", "required": true } ] diff --git a/generated/iq/index.json b/generated/iq/index.json index 7d4f3e7..7e42d63 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -8403,6 +8403,11 @@ "name": "SetVnameFailureFallback" } ], + "errorEnvelope": { + "code": 207, + "codeMin": 400, + "codeMax": 599 + }, "errorCodes": [ 207 ], @@ -9128,7 +9133,7 @@ { "method": "attrEnum", "name": "type", - "type": "string", + "type": "enum", "required": true }, { @@ -35357,61 +35362,61 @@ { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true } ], @@ -35428,61 +35433,61 @@ { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true }, { "method": "attrEnum", "name": "value", - "type": "string", + "type": "enum", "required": true } ] diff --git a/generated/manifest.json b/generated/manifest.json index 7a9423f..e049038 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -30,7 +30,7 @@ }, "notif": { "actionShapes": 181, - "actions": 58, + "actions": 47, "degraded": 5, "stanzaTags": 13, "typedContent": 22, @@ -56,12 +56,12 @@ "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "450282f9bc3405a8fa8307a44faabe5b3936185d762b1f7618c8f928ed183db4" + "sha256": "ddb565f1ec990b3b9ac5643acc43fc462524f2eb832459d3c3280969f7c014e0" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "5783eece985d5d233bd6cf86aca221e8a9a6d785953c4b7a1892031cb0b48256" + "sha256": "f39fd6773d22bdcfe871093b62a5dd3759cff4f2b04729105deab21d2f4ac4de" }, "mex": { "file": "mex/index.json", @@ -71,7 +71,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "a2e5c6c0dd6b524e1b235d6d52e48794e0f83fd53f3cd7075475cf296fc7c7b5" + "sha256": "e8c5eba1dff86c5b5ae1647a1469861c3cb6fde6e68a8bbe07ba5837e45c019f" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index df841c9..a3c2ae5 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -359,42 +359,7 @@ "required": true } ] - }, - "actions": [ - { - "wireTag": "status" - }, - { - "wireTag": "text_status" - }, - { - "wireTag": "devices" - }, - { - "wireTag": "picture" - }, - { - "wireTag": "privacy" - }, - { - "wireTag": "blocklist" - }, - { - "wireTag": "biz_opt_out_list" - }, - { - "wireTag": "tos" - }, - { - "wireTag": "disappearing_mode" - }, - { - "wireTag": "notice" - }, - { - "wireTag": "user" - } - ] + } }, { "type": "business", @@ -1375,7 +1340,7 @@ { "method": "attrEnumValues", "name": "mediatype", - "type": "string", + "type": "enum", "required": true }, { @@ -1408,7 +1373,7 @@ { "method": "attrEnumValues", "name": "mediatype", - "type": "string", + "type": "enum", "required": true }, { @@ -2882,7 +2847,7 @@ { "method": "attrEnum", "name": "type", - "type": "string", + "type": "enum", "required": true }, { diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index 56c8a58..ab04c71 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -523,7 +523,7 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" @@ -557,13 +557,24 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", "format": "int64" } }, + "errorEnvelope": { + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorArm" + }, + { + "type": "null" + } + ] + }, "errorTexts": { "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index eb589d8..ac09ace 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -588,7 +588,7 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" @@ -622,13 +622,24 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", "format": "int64" } }, + "errorEnvelope": { + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorArm" + }, + { + "type": "null" + } + ] + }, "errorTexts": { "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 1c32e4f..4fdde27 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -698,7 +698,7 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" @@ -732,13 +732,24 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", "format": "int64" } }, + "errorEnvelope": { + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorArm" + }, + { + "type": "null" + } + ] + }, "errorTexts": { "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 0af0b6c..4c08e7e 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -490,7 +490,7 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" @@ -524,13 +524,24 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", "format": "int64" } }, + "errorEnvelope": { + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorArm" + }, + { + "type": "null" + } + ] + }, "errorTexts": { "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index ed236a9..685436b 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -497,7 +497,7 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. This is the authoritative form; see [`ErrorArm`] for why the flattened\nlists below cannot express it.", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" @@ -531,13 +531,24 @@ "format": "int64" }, "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`], never one value from this\nlist and another from [`error_texts`].\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", + "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", "type": "array", "items": { "type": "integer", "format": "int64" } }, + "errorEnvelope": { + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "anyOf": [ + { + "$ref": "#/$defs/ErrorArm" + }, + { + "type": "null" + } + ] + }, "errorTexts": { "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", "type": "array", From 5830753bb1a39b3d5d3489f866c2850f7cd760d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 20:10:39 -0300 Subject: [PATCH 10/37] ir: model the error vocabulary correctly, breaking 1.x rather than working around it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contract is already going to 2.0.0 for the new `reference` assertion kind, so the two shapes that were compromises for backward compatibility — and the one that was simply wrong — are now modelled properly instead. `kind` absorbs the error side. The original request asked for `success | client_error | server_error`; I emitted `error` plus a parallel `errorClass` to keep 1.x consumers parsing. With the break taken, there is one field again: `client_error` (50) and `server_error` (54) where the codes settle it, and a plain `error` (47) where they do not — mixed families, or something outside both like the 207 partial failure. Naming that case is more honest than forcing a side, and `is_error()` groups all three for a consumer that does not care. The flattened `errorCodes` / `errorTexts` / `errorCodeMin` / `errorCodeMax` are removed rather than kept as a convenience view. Two independent lists cannot express which code goes with which text: 117 variants advertised combinations the parser rejects, and a "view over the arms" is still a loaded gun in a consumer's hands. `errorArms` is now the only representation, and the question the flat lists answered is one `.iter().any(…)` away. The envelope was mixing two nodes' pins, which is the bug review flagged. `SetResponsePreKeySuccessVnameFailure` pins `code=207` on `` and range-checks 400..=599 on the `` inside it, and `arm_pins` folded both into one `errorEnvelope` — telling a consumer that 207 must also fall in 400..=599, while the inner range hung off nothing. Pins are now partitioned by the node path they are read from: the deepest belong to the node the arms discriminate and merge into them, anything shallower is the envelope. The one affected variant now reads `errorEnvelope: {code: 207}` with each arm carrying its own `codeMin/codeMax: 400..599`. Considered and not done: making `literalValue` a typed scalar rather than the string form. That one is not a compatibility compromise but a deliberate choice — every wire value is a string, and `fieldType` already says how to read it back — so changing it would add a union type to every consumer for no gain in fidelity. Determinism holds, schemas validate, and the reference codegen is still byte-identical. --- README.md | 8 +- crates/wa-ir/src/iq.rs | 152 +- crates/wa-scan/src/response_index.rs | 223 ++- crates/whatspec/src/main.rs | 14 +- generated/iq/index.json | 2192 ++----------------------- generated/manifest.json | 4 +- generated/schema/incoming.schema.json | 89 +- generated/schema/iq.schema.json | 89 +- generated/schema/notif.schema.json | 89 +- generated/schema/srvreq.schema.json | 89 +- generated/schema/stanza.schema.json | 89 +- 11 files changed, 446 insertions(+), 2592 deletions(-) diff --git a/README.md b/README.md index fb9bd7a..fcebc99 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,15 @@ Field shapes tell you how to *read* a stanza. They are not enough to *produce* o - **Echo rules** (`assertions[].kind == "reference"`, and `referencePath` on a field) — an answer's `from` must equal the **request's** `to`, its `id` the request's `id`. `referencePath` is the argument list of WA's `attrStringFromReference`, so `["to"]` means "the request's `to`" and `["account","action"]` means "the `action` attribute of the request's `` child" — no name-matching required. An emitter that hardcodes `from="s.whatsapp.net"` makes every answer to a `g.us` request unparseable. - **Pinned values** (`literalValue` on a field) — `type="admin"` on a successful promote, `matched="true"`/`"false"` on a blocklist update, `code=429` on a rate-limit error. `required` separates the two forms: a required pin is a hard discriminator (must be present and exact); an optional one is pinned only when present (may be omitted, must never be contradicted). -- **The per-RPC error vocabulary** (`errorClass`, `errorCodes`, `errorTexts`, `errorCodeMin`/`Max` on a response variant) — a **closed** set, and it differs per RPC: `BatchGetGroupInfo` accepts `400 bad-request` and `429 rate-overlimit` and **rejects `404 item-not-found`**, even though that mixin exists and other RPCs use it. `errorCodeMin`/`Max` cover the open-ended fallback arms (any text, code in 400–499 / 500–599). +- **The per-RPC error vocabulary** (`errorArms`, plus `errorEnvelope` for a two-level error) — a **closed** set, and it differs per RPC: `BatchGetGroupInfo` accepts `400 bad-request` and `429 rate-overlimit` and **rejects `404 item-not-found`**, even though that mixin exists and other RPCs use it. Each arm pairs the `code` with the `text` that must accompany it, so an emitter cannot combine one arm's code with another's text and produce a stanza no branch accepts; an arm that range-checks instead of pinning carries `codeMin`/`codeMax`. The variant's `kind` says which side is at fault (`client_error` / `server_error`), derived from the codes rather than from the parser's name. - **Response enums** (`enumRef` on a field) — the legal values behind an `attrStringEnum`, resolved the same way the request side already resolves them, instead of a bare `"type": "enum"`. - **Notification action unions** (`notifications[].actions`) — the payload inside the envelope. The `wireTag → actionType` mapping is **many-to-one** (`not_ephemeral` normalises into `ephemeral` with `duration: 0`, so branching on `not_ephemeral` is dead code) and field names are rebound (the disappearing-message timer arrives in `expiration`, but the action field is `duration`). Neither is derivable from the wire. -Note the contract version: this raised `schemaVersion` to **2.0.0**. Nearly every addition is optional and a 1.x consumer ignores it, but `AssertionKind` gained a `reference` variant, which widens the value space of an existing field — a consumer with a closed enum (generated from the 1.0 schema, or the Rust enum) rejects the document rather than ignoring it. The migration is one variant wide: handle, or ignore, `kind: "reference"` on response assertions. +Note the contract version: this raised `schemaVersion` to **2.0.0**, and it is a real major bump rather than a cautious one. Three changes need action from a 1.x consumer: + +- `AssertionKind` gained a `reference` variant, widening the value space of an existing field — a closed-enum consumer rejects the document rather than ignoring it (validating the current `iq/index.json` against the 1.0 schema fails 579 times). +- `ResponseVariantKind` gained `client_error` and `server_error`; a variant that was `error` may now be either. Match on all three, or use the `is_error()` grouping. +- A response variant's `errorCodes` / `errorTexts` / `errorCodeMin` / `errorCodeMax` / `errorClass` are **gone**, replaced by `errorArms` (+ `errorEnvelope`). The flat lists were removed rather than kept alongside because they were unsound: two independent lists cannot say which code goes with which text, and 117 variants admitted combinations the parser rejects. Anything the extractor sees but cannot resolve structurally is counted under `manifest.diagnostics.iq.dropsByReason` rather than omitted, so "no constraint here" and "a constraint we failed to extract" never look alike. `manifest.diagnostics.iq.constraints` and `diagnostics.notif.actions` are floor-guarded: a WA refactor that hides one of these constructs fails the update instead of silently emptying a field. diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index 95b0756..044587b 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -515,48 +515,61 @@ pub struct ParsedField { } /// How a response-root union variant classifies (drives codegen `Ok`/`Err` arms). +/// +/// The error side is split by **who is at fault**, because that decides what a caller +/// should do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the +/// two as separate parsers with disjoint code ranges (`bad-request` 400, +/// `rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback +/// 500–599), so the split is the protocol's, not an interpretation. +/// +/// The side is derived from the **codes** the variant accepts, never from its name: WA +/// spells the arms inconsistently (`…ResponseServerError`, but also +/// `…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a +/// success while asserting `type="error"`). A variant whose codes are mixed or outside +/// both families stays [`Error`] — an error whose side could not be determined, which is +/// a fact worth stating rather than a coin flip. +/// +/// [`Error`]: ResponseVariantKind::Error #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum ResponseVariantKind { #[default] Success, + /// The request was wrong (4xx) — retrying it unchanged will fail again. + ClientError, + /// The server failed (5xx) — the same request may succeed later. + ServerError, + /// An error whose side the accepted codes do not determine. Error, /// A structured non-happy outcome that still parses (Nack, Conflict, …). Alternative, } -/// Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is -/// [`Error`]. WA models the two as separate parsers with disjoint code ranges: a client -/// error carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499) -/// and a server error a 5xx one (`internal-server-error` 500, fallback 500–599). -/// -/// Emitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that -/// only understands `success`/`error` keeps working. -/// -/// [`Error`]: ResponseVariantKind::Error -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[serde(rename_all = "snake_case")] -pub enum ErrorClass { - /// The request was wrong (4xx) — retrying it unchanged will fail again. - Client, - /// The server failed (5xx) — the same request may succeed later. - Server, +impl ResponseVariantKind { + /// Whether this is any error outcome, whichever side is at fault. + pub fn is_error(&self) -> bool { + matches!( + self, + ResponseVariantKind::Error + | ResponseVariantKind::ClientError + | ResponseVariantKind::ServerError + ) + } } /// One accepted `` shape of a response variant: the `code` and `text` that must /// occur **together**. /// -/// The flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer "what -/// values does this RPC accept?", but they cannot say which value goes with which: an -/// arm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes -/// and two texts, and nothing then rules out `400 feature-not-implemented` — a -/// combination the parser rejects. An emitter that picks one value from each list -/// therefore produces an unparseable stanza, which is the exact failure class this -/// domain exists to prevent. The pairing lives here. +/// A variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be +/// described by two independent lists — nothing would then rule out `400 +/// feature-not-implemented`, a combination the parser rejects, so an emitter picking one +/// value from each list produces an unparseable stanza. The pairing is the point. /// -/// [`error_texts`]: ResponseVariant::error_texts +/// Every field is optional because each is a genuine shape: an arm may pin an exact +/// `(code, text)`, pin only the code and read the text freely +/// (`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and +/// range-check the code (the `IQErrorFallback*` arms). #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -569,23 +582,16 @@ pub struct ErrorArm { /// The exact `code` this arm pins, when it pins one. #[serde(default, skip_serializing_if = "Option::is_none")] pub code: Option, - /// The exact `text` this arm pins, when it pins one. - /// - /// Absent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins - /// neither value and accepts any text within [`code_min`]..=[`code_max`], while a - /// code-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail` - /// pins `code=548` and accepts whatever `text` the server sends with it. In both, an - /// emitter is free to choose the text; it is only [`code`] that is constrained. - /// - /// [`code`]: ErrorArm::code - /// [`code_min`]: ErrorArm::code_min - /// [`code_max`]: ErrorArm::code_max + /// The exact `text` this arm pins. Absent is a fact, not a gap — see the note above. #[serde(default, skip_serializing_if = "Option::is_none")] pub text: Option, - /// Inclusive lower bound of a fallback arm's accepted code range. + /// Inclusive lower bound of the codes this arm accepts, when it range-checks instead + /// of pinning. #[serde(default, skip_serializing_if = "Option::is_none")] pub code_min: Option, - /// Inclusive upper bound of a fallback arm's accepted code range. + /// Inclusive upper bound of the same range (see [`code_min`]). + /// + /// [`code_min`]: ErrorArm::code_min #[serde(default, skip_serializing_if = "Option::is_none")] pub code_max: Option, } @@ -601,18 +607,19 @@ pub struct ResponseVariant { pub tag: String, pub module_name: String, pub kind: ResponseVariantKind, - /// For an error variant, whether it is the client-error (4xx) or server-error (5xx) - /// arm. Absent for a success variant, and for an error variant whose parser carries - /// no recoverable code evidence. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error_class: Option, /// The accepted `` shapes, in parser order — **which code goes with which - /// text**. See [`ErrorArm`] for why the flattened lists below cannot express it. + /// text**. See [`ErrorArm`]; this is the only representation of the vocabulary, + /// deliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced + /// let an emitter combine one arm's code with another's text and produce a stanza no + /// branch accepts. /// /// An arm describes the *discriminating* `` node. When the variant also pins /// the node above it, those pins are in [`error_envelope`], and the full shape is - /// **arm + envelope** — an arm alone is not enough to build a response for such a - /// variant. + /// **arm + envelope**. + /// + /// To answer "does this RPC accept code N / text T?", scan the arms — the question + /// the flat lists used to answer is one `.iter().any(…)` away, and asking it that way + /// cannot produce an invalid pair. /// /// [`error_envelope`]: ResponseVariant::error_envelope #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -620,55 +627,14 @@ pub struct ResponseVariant { /// Pins on the `` node **enclosing** the one the arms discriminate, for a /// two-level error. /// - /// `SetResponsePreKeySuccessVnameFailure` is the shape: the response carries - /// `` — the partial-failure envelope, shared by every arm — around - /// an inner `` whose text the disjunction pins. The envelope belongs to no - /// single arm, so duplicating it into all of them would misreport where the code - /// sits; it is recorded once here instead. Absent for the ordinary one-level error, - /// which is nearly all of them. + /// `SetResponsePreKeySuccessVnameFailure` is the shape: `` — the + /// partial-failure envelope, shared by every arm — wrapping an inner `` whose + /// text the disjunction pins and whose code is range-checked. Pins are partitioned by + /// the node they are read from, so the envelope holds only the enclosing node's and + /// the inner node's shared constraints stay on the arms; mixing them would tell a + /// consumer that code 207 must also fall in 400–599. #[serde(default, skip_serializing_if = "Option::is_none")] pub error_envelope: Option, - /// The **closed set** of `` values this variant accepts, ascending — the - /// union over [`error_arms`], for the "does this RPC accept code N?" question. - /// - /// The vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm - /// takes only `400` and `429` and **rejects `404`**, even though an - /// `IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code - /// outside this set matches no branch, so the client reports a parse failure rather - /// than the error. Empty when the variant pins no exact code (see [`error_code_min`]). - /// - /// An emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when - /// present), never one value from this list and another from [`error_texts`]. - /// - /// [`error_envelope`]: ResponseVariant::error_envelope - /// - /// [`error_arms`]: ResponseVariant::error_arms - /// [`error_texts`]: ResponseVariant::error_texts - /// [`error_code_min`]: ResponseVariant::error_code_min - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub error_codes: Vec, - /// The `` values this variant accepts, sorted — the union over - /// [`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning - /// on [`error_codes`] about combining the two lists. - /// - /// [`error_arms`]: ResponseVariant::error_arms - /// [`error_codes`]: ResponseVariant::error_codes - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub error_texts: Vec, - /// Lower bound of the open-ended fallback arm's code range, when the variant has one - /// (`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its - /// client twin accepts 400–499). Together with [`error_code_max`] this says "any - /// code in this range is also accepted", which [`error_codes`] alone cannot express. - /// - /// [`error_code_max`]: ResponseVariant::error_code_max - /// [`error_codes`]: ResponseVariant::error_codes - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error_code_min: Option, - /// Upper bound of the fallback arm's code range (see [`error_code_min`]). - /// - /// [`error_code_min`]: ResponseVariant::error_code_min - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error_code_max: Option, pub assertions: Vec, pub fields: Vec, } diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index f54cf5c..e9bd72b 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -13,8 +13,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use wa_ir::{ - ErrorArm, ErrorClass, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, - ResponseVariantKind, + ErrorArm, ParsedField, ParsedFieldType, ParsedResponse, ResponseVariant, ResponseVariantKind, }; use crate::response_smax::{ @@ -153,7 +152,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd // or `text` field for unrelated reasons (three PreKeys success variants do), // and reporting that as an accepted error vocabulary contradicts the // per-error-arm meaning documented on `ResponseVariant`. - let vocab = if kind == ResponseVariantKind::Error { + let vocab = if kind.is_error() { error_vocabulary(&fields) } else { ErrorVocabulary::default() @@ -161,14 +160,11 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd variants.push(ResponseVariant { tag, module_name: module, - kind, - error_class: vocab.class(), + // The side at fault is a property of the codes, so it is settled only + // once the vocabulary is read. + kind: vocab.refine(kind), error_arms: vocab.arms, error_envelope: vocab.envelope, - error_codes: vocab.codes, - error_texts: vocab.texts, - error_code_min: vocab.code_min, - error_code_max: vocab.code_max, assertions, fields, }); @@ -220,45 +216,43 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd /// disjunction. #[derive(Default)] struct ErrorVocabulary { - /// The accepted `(code, text)` shapes, in parser order — the authoritative form. + /// The accepted `(code, text)` shapes, in parser order. arms: Vec, /// Pins on the `` node enclosing the ones the arms discriminate, for a /// two-level error. Recorded once rather than duplicated into every arm. envelope: Option, - /// Flattened unions over `arms`, kept for the "does this RPC accept N?" question. - codes: Vec, - texts: Vec, - code_min: Option, - code_max: Option, } impl ErrorVocabulary { - /// Which side the codes blame: 4xx is the client's fault, 5xx the server's. Derived - /// from the evidence rather than the variant's name, since WA spells the two arms - /// inconsistently (`…ResponseServerError` but also `…ResponseInternalServerError`). - /// `None` when the arm pins no code at all, or mixes both families (never guessed). - fn class(&self) -> Option { + /// Refine `Error` into the side at fault: 4xx is the client's, 5xx the server's. + /// Derived from every code the variant accepts — arms and envelope alike — so a + /// variant that mixes families, or pins something outside both (the 207 partial + /// failure), stays a plain `Error` rather than being guessed into a side. + fn refine(&self, kind: ResponseVariantKind) -> ResponseVariantKind { + if kind != ResponseVariantKind::Error { + return kind; + } let of = |c: i64| match c { - 400..=499 => Some(ErrorClass::Client), - 500..=599 => Some(ErrorClass::Server), + 400..=499 => Some(ResponseVariantKind::ClientError), + 500..=599 => Some(ResponseVariantKind::ServerError), _ => None, }; - let mut seen: Option = None; + let mut seen: Option = None; for c in self - .codes + .arms .iter() - .copied() - .chain(self.code_min) - .chain(self.code_max) + .chain(self.envelope.as_ref()) + .flat_map(|a| [a.code, a.code_min, a.code_max]) + .flatten() { match (of(c), seen) { - (None, _) => return None, + (None, _) => return ResponseVariantKind::Error, (Some(cur), None) => seen = Some(cur), (Some(cur), Some(prev)) if cur == prev => {} - _ => return None, + _ => return ResponseVariantKind::Error, } } - seen + seen.unwrap_or(ResponseVariantKind::Error) } } @@ -280,33 +274,76 @@ impl ErrorVocabulary { fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut v = ErrorVocabulary::default(); let from_union = collect_error_arms(fields, &mut v.arms); - // A two-level error — an outer `` wrapping an inner `` whose - // text the disjunction pins — has pins the ARMS cannot carry: an arm describes one - // discriminating node, and the envelope belongs to none of them. Recorded once, so an - // "arm + envelope" reading is complete; duplicating it into every arm would misreport - // where the code sits. - // Only when the arms came from a DISJUNCTION are the variant's own pins a separate, - // enclosing node. Without a disjunction those same pins ARE the single arm, and - // recording them twice would invent an envelope the response does not have. - let envelope = arm_pins(fields); - if from_union && envelope != ErrorArm::default() { - v.envelope = Some(envelope.clone()); - } - for a in v.arms.iter().chain(std::iter::once(&envelope)) { - v.codes.extend(a.code); - v.texts.extend(a.text.clone()); - if let (Some(min), Some(max)) = (a.code_min, a.code_max) { - v.code_min = Some(v.code_min.map_or(min, |cur: i64| cur.min(min))); - v.code_max = Some(v.code_max.map_or(max, |cur: i64| cur.max(max))); + // Partition the variant's own (non-alternative) pins by the NODE they are read from. + // `SetResponsePreKeySuccessVnameFailure` pins `code=207` on `` and + // range-checks `code` 400..=599 on the `` inside it: folding both into + // one envelope tells a consumer that 207 must also fall in 400..=599, and leaves the + // inner range attached to nothing. The deepest pins belong to the node the arms + // discriminate and are merged into them; anything shallower is the envelope. + let mut by_path: std::collections::BTreeMap, ErrorArm> = Default::default(); + pins_by_path(fields, &[], &mut by_path); + if from_union && !by_path.is_empty() { + let deepest = by_path.keys().map(Vec::len).max().unwrap_or(0); + let (inner, outer): (Vec<_>, Vec<_>) = by_path + .into_iter() + .partition(|(path, _)| path.len() == deepest && deepest > 0); + // Shared constraints of the discriminating node: every arm is subject to them. + for (_, shared) in &inner { + for arm in &mut v.arms { + arm.code = arm.code.or(shared.code); + arm.text = arm.text.clone().or_else(|| shared.text.clone()); + arm.code_min = arm.code_min.or(shared.code_min); + arm.code_max = arm.code_max.or(shared.code_max); + } + } + let mut envelope = ErrorArm::default(); + for (_, outer_pins) in outer { + envelope.code = envelope.code.or(outer_pins.code); + envelope.text = envelope.text.or(outer_pins.text); + envelope.code_min = envelope.code_min.or(outer_pins.code_min); + envelope.code_max = envelope.code_max.or(outer_pins.code_max); + } + if envelope != ErrorArm::default() { + v.envelope = Some(envelope); } } - v.codes.sort_unstable(); - v.codes.dedup(); - v.texts.sort(); - v.texts.dedup(); v } +/// The `code`/`text` pins of the variant's own fields, grouped by the node path they are +/// read from. Disjunction alternatives are skipped — each of those is its own arm. +fn pins_by_path( + fields: &[ParsedField], + base: &[String], + out: &mut std::collections::BTreeMap, ErrorArm>, +) { + for f in fields { + let mut path = base.to_vec(); + path.extend(f.source_path.iter().flatten().cloned()); + match (f.wire_name.as_deref().unwrap_or(&f.name), &f.literal_value) { + ("code", Some(lit)) => { + if let Ok(code) = lit.parse::() { + out.entry(path.clone()).or_default().code = Some(code); + } + } + ("text", Some(lit)) => { + out.entry(path.clone()).or_default().text = Some(lit.clone()); + } + ("code", None) => { + if let (Some(min), Some(max)) = (f.int_min, f.int_max) { + let e = out.entry(path.clone()).or_default(); + e.code_min = Some(min); + e.code_max = Some(max); + } + } + _ => {} + } + if let Some(children) = &f.children { + pins_by_path(children, &path, out); + } + } +} + /// One arm per accepted `` shape, so the `(code, text)` pairing survives. /// /// A `…Errors` disjunction contributes one arm per alternative; an RPC that parses a @@ -583,25 +620,27 @@ mod tests { .find(|v| v.tag == tag) .unwrap_or_else(|| panic!("no variant {tag}")) }; - // A success variant carries no error vocabulary and no error class. + // A success variant carries no error vocabulary. let ok = v("GetBarResponseSuccess"); assert_eq!(ok.kind, ResponseVariantKind::Success); - assert!(ok.error_class.is_none()); - assert!(ok.error_codes.is_empty() && ok.error_texts.is_empty()); + assert!(ok.error_arms.is_empty() && ok.error_envelope.is_none()); // The client arm's vocabulary is CLOSED: exactly what its disjunction accepts. // Answering `404 item-not-found` here matches no branch — the bug this exists // to make visible — so 404 must not appear. let ce = v("GetBarResponseClientError"); - assert_eq!(ce.error_class, Some(ErrorClass::Client)); - assert_eq!(ce.error_codes, vec![400]); - assert_eq!(ce.error_texts, vec!["bad-request".to_string()]); - assert_eq!((ce.error_code_min, ce.error_code_max), (None, None)); + assert_eq!(ce.kind, ResponseVariantKind::ClientError); + assert_eq!(ce.error_arms.len(), 1); + assert_eq!( + (ce.error_arms[0].code, ce.error_arms[0].text.as_deref()), + (Some(400), Some("bad-request")) + ); // The server arm is the open-ended fallback: any text, any code in 500..=599. let se = v("GetBarResponseServerError"); - assert_eq!(se.error_class, Some(ErrorClass::Server)); - assert!(se.error_codes.is_empty(), "a range is not an exact code"); + assert_eq!(se.kind, ResponseVariantKind::ServerError); + assert_eq!(se.error_arms.len(), 1); + assert_eq!(se.error_arms[0].code, None, "a range is not an exact code"); assert_eq!( - (se.error_code_min, se.error_code_max), + (se.error_arms[0].code_min, se.error_arms[0].code_max), (Some(500), Some(599)) ); } @@ -626,9 +665,6 @@ mod tests { (arm.code, arm.text.as_deref()), (Some(400), Some("bad-request")) ); - // The flat lists stay a VIEW over the arms, so the two can never disagree. - assert_eq!(ce.error_codes, vec![400]); - assert_eq!(ce.error_texts, vec!["bad-request".to_string()]); // A fallback arm pins a range and NO text: it accepts any text in 500..=599, so // a `text` of `None` there is the fact, not a gap. @@ -718,32 +754,45 @@ mod tests { } #[test] - fn error_class_is_derived_from_codes_not_from_the_variant_name() { - // Codes decide, so `…ResponseInternalServerError` (which reads like a server - // arm but is spelled inconsistently across namespaces) can't be misfiled. - let v = ErrorVocabulary { - codes: vec![400, 429], + fn the_error_side_is_derived_from_codes_not_from_the_variant_name() { + // The codes decide, so `…ResponseInternalServerError` (which reads like a server + // arm but is spelled inconsistently across namespaces) cannot be misfiled, and a + // variant whose codes settle nothing stays a plain `Error` rather than a guess. + let arm = |code: Option, min: Option, max: Option| ErrorArm { + code, + code_min: min, + code_max: max, ..Default::default() }; - assert_eq!(v.class(), Some(ErrorClass::Client)); - let v = ErrorVocabulary { - code_min: Some(500), - code_max: Some(599), - ..Default::default() + let vocab = |arms: Vec| ErrorVocabulary { + arms, + envelope: None, }; - assert_eq!(v.class(), Some(ErrorClass::Server)); + let refine = |arms: Vec| vocab(arms).refine(ResponseVariantKind::Error); + + assert_eq!( + refine(vec![arm(Some(400), None, None), arm(Some(429), None, None)]), + ResponseVariantKind::ClientError + ); + assert_eq!( + refine(vec![arm(None, Some(500), Some(599))]), + ResponseVariantKind::ServerError + ); // Mixed families, or a code outside both, is not classified — never guessed. - let v = ErrorVocabulary { - codes: vec![400, 500], - ..Default::default() - }; - assert_eq!(v.class(), None); - let v = ErrorVocabulary { - codes: vec![304], - ..Default::default() - }; - assert_eq!(v.class(), None); - assert_eq!(ErrorVocabulary::default().class(), None); + assert_eq!( + refine(vec![arm(Some(400), None, None), arm(Some(500), None, None)]), + ResponseVariantKind::Error + ); + assert_eq!( + refine(vec![arm(Some(207), None, None)]), + ResponseVariantKind::Error + ); + assert_eq!(refine(vec![]), ResponseVariantKind::Error); + // A success variant is never touched. + assert_eq!( + vocab(vec![arm(Some(400), None, None)]).refine(ResponseVariantKind::Success), + ResponseVariantKind::Success + ); } #[test] diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index fa907e5..faa9dc6 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -876,11 +876,19 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { } for v in &s.response.variants { c.reference_constraints += count_references(&v.assertions); - if v.error_class.is_some() { + if matches!( + v.kind, + wa_ir::ResponseVariantKind::ClientError | wa_ir::ResponseVariantKind::ServerError + ) { c.typed_error_variants += 1; } - texts.extend(v.error_texts.iter().cloned()); - c.error_arms += v.error_arms.len(); + texts.extend( + v.error_arms + .iter() + .chain(v.error_envelope.as_ref()) + .filter_map(|a| a.text.clone()), + ); + c.error_arms += v.error_arms.len() + usize::from(v.error_envelope.is_some()); walk_fields(&v.fields, &mut c); } } diff --git a/generated/iq/index.json b/generated/iq/index.json index 7e42d63..d1e4859 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -458,14 +458,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 501 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented" - ], "assertions": [ { "kind": "tag", @@ -590,20 +582,13 @@ { "tag": "GetExperimentConfigResponseErrorRetry", "moduleName": "WASmaxInAbPropsGetExperimentConfigResponseErrorRetry", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -1013,14 +998,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 501 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented" - ], "assertions": [ { "kind": "tag", @@ -1145,20 +1122,13 @@ { "tag": "GetGroupExperimentConfigResponseErrorRetry", "moduleName": "WASmaxInAbPropsGetGroupExperimentConfigResponseErrorRetry", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -2063,8 +2033,7 @@ { "tag": "GetBlockListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsGetBlockListResponseInvalidRequest", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -2077,14 +2046,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 429 - ], - "errorTexts": [ - "bad-request", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -2209,8 +2170,7 @@ { "tag": "GetBlockListResponseInternalServerError", "moduleName": "WASmaxInBlocklistsGetBlockListResponseInternalServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorFeatureNotImplemented", @@ -2223,14 +2183,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 500, - 501 - ], - "errorTexts": [ - "feature-not-implemented", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -3072,8 +3024,7 @@ { "tag": "UpdateBlockListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseInvalidRequest", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -3101,20 +3052,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 403, - 405, - 406, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "not-acceptable", - "not-allowed", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -3398,8 +3335,7 @@ { "tag": "UpdateBlockListResponseServerError", "moduleName": "WASmaxInBlocklistsUpdateBlockListResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorFeatureNotImplemented", @@ -3412,14 +3348,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 500, - 501 - ], - "errorTexts": [ - "feature-not-implemented", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -4214,18 +4142,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 403, - 405, - 500 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "not-allowed" - ], "assertions": [ { "kind": "tag", @@ -5718,8 +5634,7 @@ { "tag": "FetchKeyBundlesResponseRequestError", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseRequestError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -5737,16 +5652,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 406 - ], - "errorTexts": [ - "bad-request", - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -5898,8 +5803,7 @@ { "tag": "FetchKeyBundlesResponseServerError", "moduleName": "WASmaxInPreKeysFetchKeyBundlesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorServiceUnavailable", @@ -5912,14 +5816,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -6920,8 +6816,7 @@ { "tag": "FetchMissingPreKeysResponseRequestError", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseRequestError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -6939,16 +6834,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 406 - ], - "errorTexts": [ - "bad-request", - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -7100,8 +6985,7 @@ { "tag": "FetchMissingPreKeysResponseServerError", "moduleName": "WASmaxInPreKeysFetchMissingPreKeysResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorServiceUnavailable", @@ -7114,14 +6998,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -7539,8 +7415,7 @@ { "tag": "AddResponseRequestError", "moduleName": "WASmaxInPreKeysAddResponseRequestError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -7553,14 +7428,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 406 - ], - "errorTexts": [ - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -7699,8 +7566,7 @@ { "tag": "AddResponseServerError", "moduleName": "WASmaxInPreKeysAddResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorServiceUnavailable", @@ -7713,14 +7579,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -7936,8 +7794,7 @@ { "tag": "DeleteResponseRequestError", "moduleName": "WASmaxInPreKeysDeleteResponseRequestError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -7950,14 +7807,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 406 - ], - "errorTexts": [ - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -8096,8 +7945,7 @@ { "tag": "DeleteResponseServerError", "moduleName": "WASmaxInPreKeysDeleteResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorServiceUnavailable", @@ -8110,14 +7958,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -8389,35 +8229,31 @@ "errorArms": [ { "name": "SetVnameFailureNotAcceptable", - "text": "not-acceptable" + "text": "not-acceptable", + "codeMin": 400, + "codeMax": 599 }, { "name": "SetVnameFailureBadRequest", - "text": "bad-request" + "text": "bad-request", + "codeMin": 400, + "codeMax": 599 }, { "name": "SetVnameFailureInternalServerError", - "text": "internal-server-error" + "text": "internal-server-error", + "codeMin": 400, + "codeMax": 599 }, { - "name": "SetVnameFailureFallback" + "name": "SetVnameFailureFallback", + "codeMin": 400, + "codeMax": 599 } ], "errorEnvelope": { - "code": 207, - "codeMin": 400, - "codeMax": 599 + "code": 207 }, - "errorCodes": [ - 207 - ], - "errorTexts": [ - "bad-request", - "internal-server-error", - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -8623,8 +8459,7 @@ { "tag": "SetResponseRequestError", "moduleName": "WASmaxInPreKeysSetResponseRequestError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -8637,14 +8472,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 406 - ], - "errorTexts": [ - "not-acceptable" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -8783,8 +8610,7 @@ { "tag": "SetResponseServerError", "moduleName": "WASmaxInPreKeysSetResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorServiceUnavailable", @@ -8797,14 +8623,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -9377,20 +9195,13 @@ { "tag": "GetAccessTokenAndSessionCookiesResponseTooManyAttempts", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseTooManyAttempts", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 431, "text": "TOO_MANY_ATTEMPTS" } ], - "errorCodes": [ - 431 - ], - "errorTexts": [ - "TOO_MANY_ATTEMPTS" - ], "assertions": [ { "kind": "tag", @@ -9459,20 +9270,13 @@ { "tag": "GetAccessTokenAndSessionCookiesResponseIncorrectNonce", "moduleName": "WASmaxInBizCtwaAdAccountGetAccessTokenAndSessionCookiesResponseIncorrectNonce", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 432, "text": "INCORRECT_NONCE" } ], - "errorCodes": [ - 432 - ], - "errorTexts": [ - "INCORRECT_NONCE" - ], "assertions": [ { "kind": "tag", @@ -9564,18 +9368,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 403, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -9964,16 +9756,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 400, - 475, - 500 - ], - "errorTexts": [ - "bad-request", - "internal-server-error", - "notice-required" - ], "assertions": [ { "kind": "tag", @@ -11139,20 +10921,13 @@ { "tag": "GetLinkedAccountsResponseForbidden", "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseForbidden", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 403, "text": "forbidden" } ], - "errorCodes": [ - 403 - ], - "errorTexts": [ - "forbidden" - ], "assertions": [ { "kind": "tag", @@ -11225,20 +11000,13 @@ { "tag": "GetLinkedAccountsResponseError", "moduleName": "WASmaxInBizLinkingGetLinkedAccountsResponseError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -11511,18 +11279,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 403, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -11904,18 +11660,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 403, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -12751,18 +12495,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 403, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -13134,14 +12866,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 400, - 500 - ], - "errorTexts": [ - "bad-request", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -13382,20 +13106,13 @@ { "tag": "GetPasskeyRequestOptionsResponseError", "moduleName": "WASmaxInMdGetPasskeyRequestOptionsResponseError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -13683,14 +13400,6 @@ "text": "bad-request" } ], - "errorCodes": [ - 400, - 500 - ], - "errorTexts": [ - "bad-request", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -14020,20 +13729,6 @@ "text": "forbidden" } ], - "errorCodes": [ - 400, - 403, - 429, - 452, - 500 - ], - "errorTexts": [ - "bad-request", - "feature-not-available", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -20140,8 +19835,7 @@ { "tag": "GetNewsletterMessageUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -20169,20 +19863,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 404, - 423, - 429, - 451 - ], - "errorTexts": [ - "bad-request", - "features-disabled", - "item-not-found", - "locked", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -20523,20 +20203,13 @@ { "tag": "GetNewsletterMessageUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterMessageUpdatesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -26202,8 +25875,7 @@ { "tag": "GetNewsletterMessagesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -26236,22 +25908,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 404, - 405, - 423, - 429, - 451 - ], - "errorTexts": [ - "bad-request", - "features-disabled", - "item-not-found", - "locked", - "not-allowed", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -26656,20 +26312,13 @@ { "tag": "GetNewsletterMessagesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterMessagesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -27163,8 +26812,7 @@ { "tag": "GetNewsletterResponsesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -27197,22 +26845,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 401, - 404, - 405, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "locked", - "not-allowed", - "not-authorized", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -27617,20 +27249,13 @@ { "tag": "GetNewsletterResponsesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterResponsesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -28839,8 +28464,7 @@ { "tag": "GetNewsletterStatusUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -28868,20 +28492,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 404, - 423, - 429, - 451 - ], - "errorTexts": [ - "bad-request", - "features-disabled", - "item-not-found", - "locked", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -29222,20 +28832,13 @@ { "tag": "GetNewsletterStatusUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusUpdatesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -30509,8 +30112,7 @@ { "tag": "GetNewsletterStatusesResponseClientError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -30543,22 +30145,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 404, - 405, - 423, - 429, - 451 - ], - "errorTexts": [ - "bad-request", - "features-disabled", - "item-not-found", - "locked", - "not-allowed", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -30963,20 +30549,13 @@ { "tag": "GetNewsletterStatusesResponseServerError", "moduleName": "WASmaxInNewslettersGetNewsletterStatusesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -31347,8 +30926,7 @@ { "tag": "MyAddOnsResponseClientError", "moduleName": "WASmaxInNewslettersMyAddOnsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -31371,18 +30949,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 401, - 404, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-authorized", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -31659,20 +31225,13 @@ { "tag": "MyAddOnsResponseServerError", "moduleName": "WASmaxInNewslettersMyAddOnsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -31993,8 +31552,7 @@ { "tag": "StatusMyAddOnsResponseClientError", "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -32017,18 +31575,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 401, - 404, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-authorized", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -32305,20 +31851,13 @@ { "tag": "StatusMyAddOnsResponseServerError", "moduleName": "WASmaxInNewslettersStatusMyAddOnsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -32488,8 +32027,7 @@ { "tag": "SubscribeToLiveUpdatesResponseClientError", "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "BadRequestIQErrorResponse", @@ -32517,20 +32055,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 404, - 423, - 429, - 451 - ], - "errorTexts": [ - "bad-request", - "features-disabled", - "item-not-found", - "locked", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -32871,20 +32395,13 @@ { "tag": "SubscribeToLiveUpdatesResponseServerError", "moduleName": "WASmaxInNewslettersSubscribeToLiveUpdatesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -33253,8 +32770,7 @@ { "tag": "GetOptOutListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInvalidRequest", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -33267,14 +32783,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 429 - ], - "errorTexts": [ - "bad-request", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -33399,8 +32907,7 @@ { "tag": "GetOptOutListResponseInternalServerError", "moduleName": "WASmaxInBlocklistsGetOptOutListResponseInternalServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorFeatureNotImplemented", @@ -33413,14 +32920,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 500, - 501 - ], - "errorTexts": [ - "feature-not-implemented", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -33999,8 +33498,7 @@ { "tag": "UpdateOptOutListResponseInvalidRequest", "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseInvalidRequest", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -34023,18 +33521,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 403, - 406, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "not-acceptable", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -34257,8 +33743,7 @@ { "tag": "UpdateOptOutListResponseServerError", "moduleName": "WASmaxInBlocklistsUpdateOptOutListResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorFeatureNotImplemented", @@ -34271,14 +33756,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 500, - 501 - ], - "errorTexts": [ - "feature-not-implemented", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -34955,20 +34432,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 400, - 429, - 500, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "internal-server-error", - "rate-overlimit", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -36086,16 +35549,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -36257,20 +35710,13 @@ { "tag": "SignCredentialResponseErrorRetry", "moduleName": "WASmaxInPrivatestatsSignCredentialResponseErrorRetry", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -36625,21 +36071,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 403, - 429, - 500, - 501, - 548 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -37102,21 +36533,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 403, - 429, - 500, - 501, - 548 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -37570,21 +36986,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 403, - 429, - 500, - 501, - 548 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -38032,21 +37433,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 403, - 429, - 500, - 501, - 548 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -38479,18 +37865,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 403, - 429, - 500 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -38917,20 +38291,13 @@ { "tag": "GetDisclosureStageByIdsResponseClientError", "moduleName": "WASmaxInUserNoticeGetDisclosureStageByIdsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 400, "text": "bad-request" } ], - "errorCodes": [ - 400 - ], - "errorTexts": [ - "bad-request" - ], "assertions": [ { "kind": "tag", @@ -39009,14 +38376,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 429, - 500 - ], - "errorTexts": [ - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -39321,20 +38680,13 @@ { "tag": "GetDisclosuresResponseClientError", "moduleName": "WASmaxInUserNoticeGetDisclosuresResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 400, "text": "bad-request" } ], - "errorCodes": [ - 400 - ], - "errorTexts": [ - "bad-request" - ], "assertions": [ { "kind": "tag", @@ -39413,14 +38765,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 429, - 500 - ], - "errorTexts": [ - "internal-server-error", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -40502,22 +39846,6 @@ "text": "bad-request" } ], - "errorCodes": [ - 400, - 403, - 443, - 453, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "config-mismatch", - "forbidden", - "internal-server-error", - "service-unavailable", - "upgrade-required" - ], "assertions": [ { "kind": "tag", @@ -41301,20 +40629,13 @@ { "tag": "SetResponseInternalServerError", "moduleName": "WASmaxInPushConfigSetResponseInternalServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 500, "text": "internal-server-error" } ], - "errorCodes": [ - 500 - ], - "errorTexts": [ - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -41380,20 +40701,13 @@ { "tag": "SetResponseConflict", "moduleName": "WASmaxInPushConfigSetResponseConflict", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "code": 409, "text": "conflict" } ], - "errorCodes": [ - 409 - ], - "errorTexts": [ - "conflict" - ], "assertions": [ { "kind": "tag", @@ -42500,18 +41814,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 403, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -42916,18 +42218,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 400, - 500, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -43913,24 +43203,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 401, - 405, - 429, - 482, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-limit", - "internal-server-error", - "not-allowed", - "not-authorized", - "rate-overlimit", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -44446,18 +43718,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 400, - 500, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "internal-server-error", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -45385,8 +44645,7 @@ { "tag": "UpdatePreferenceResponseInvalidRequest", "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseInvalidRequest", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorNotAcceptable", @@ -45409,18 +44668,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 403, - 406, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "not-acceptable", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -45643,8 +44890,7 @@ { "tag": "UpdatePreferenceResponseServerError", "moduleName": "WASmaxInBizMsgUserFeedbackUpdatePreferenceResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorFeatureNotImplemented", @@ -45657,14 +44903,6 @@ "text": "internal-server-error" } ], - "errorCodes": [ - 500, - 501 - ], - "errorTexts": [ - "feature-not-implemented", - "internal-server-error" - ], "assertions": [ { "kind": "tag", @@ -45908,16 +45146,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 408, - 500, - 503 - ], - "errorTexts": [ - "internal-server-error", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -46237,18 +45465,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 408, - 429, - 500, - 503 - ], - "errorTexts": [ - "internal-server-error", - "rate-overlimit", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -46604,18 +45820,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 408, - 429, - 500, - 503 - ], - "errorTexts": [ - "internal-server-error", - "rate-overlimit", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -50306,8 +49510,7 @@ { "tag": "BatchGetGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -50320,14 +49523,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 429 - ], - "errorTexts": [ - "bad-request", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -50452,8 +49647,7 @@ { "tag": "BatchGetGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsBatchGetGroupInfoResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -50476,18 +49670,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -53920,8 +53102,7 @@ { "tag": "GetGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsGetGroupInfoResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorGone", @@ -53954,22 +53135,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 403, - 404, - 410, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "gone", - "item-not-found", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -54232,8 +53397,7 @@ { "tag": "GetGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsGetGroupInfoResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -54256,18 +53420,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -55043,8 +54195,7 @@ { "tag": "GetGroupProfilePicturesResponseClientError", "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorItemNotFound", @@ -55077,22 +54228,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 403, - 404, - 405, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "not-allowed", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -55355,8 +54490,7 @@ { "tag": "GetGroupProfilePicturesResponseServerError", "moduleName": "WASmaxInGroupsGetGroupProfilePicturesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -55379,18 +54513,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -57915,8 +57037,7 @@ { "tag": "GetInviteGroupInfoResponseClientError", "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -57964,28 +57085,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 404, - 406, - 410, - 416, - 423, - 436 - ], - "errorTexts": [ - "bad-request", - "gone", - "growth-locked", - "item-not-found", - "locked", - "not-acceptable", - "not-authorized", - "parent-group-suspended" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -58408,8 +57507,7 @@ { "tag": "GetInviteGroupInfoResponseServerError", "moduleName": "WASmaxInGroupsGetInviteGroupInfoResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -58432,18 +57530,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -61191,8 +60277,7 @@ { "tag": "GetLinkedGroupResponseClientError", "moduleName": "WASmaxInGroupsGetLinkedGroupResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorItemNotFound", @@ -61220,20 +60305,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 403, - 404, - 405 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "not-allowed" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -61459,8 +60530,7 @@ { "tag": "GetLinkedGroupResponseServerError", "moduleName": "WASmaxInGroupsGetLinkedGroupResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -61483,18 +60553,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -61895,8 +60953,7 @@ { "tag": "GetLinkedGroupsParticipantsResponseClientError", "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -61919,18 +60976,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 401, - 404, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-authorized", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -62129,8 +61174,7 @@ { "tag": "GetLinkedGroupsParticipantsResponseServerError", "moduleName": "WASmaxInGroupsGetLinkedGroupsParticipantsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -62153,18 +61197,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -62809,8 +61841,7 @@ { "tag": "GetMembershipApprovalRequestsResponseClientError", "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -62848,24 +61879,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -63165,8 +62178,7 @@ { "tag": "GetMembershipApprovalRequestsResponseServerError", "moduleName": "WASmaxInGroupsGetMembershipApprovalRequestsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -63189,18 +62201,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -66752,8 +65752,7 @@ { "tag": "GetParticipatingGroupsResponseClientError", "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -66771,16 +65770,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 429 - ], - "errorTexts": [ - "bad-request", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -66932,8 +65921,7 @@ { "tag": "GetParticipatingGroupsResponseServerError", "moduleName": "WASmaxInGroupsGetParticipatingGroupsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -66956,18 +65944,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -67504,8 +66480,7 @@ { "tag": "GetReportedMessagesResponseClientError", "moduleName": "WASmaxInGroupsGetReportedMessagesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -67533,20 +66508,6 @@ "text": "locked" } ], - "errorCodes": [ - 400, - 401, - 404, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "locked", - "not-authorized", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -67782,8 +66743,7 @@ { "tag": "GetReportedMessagesResponseServerError", "moduleName": "WASmaxInGroupsGetReportedMessagesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -67806,18 +66766,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -68388,36 +67336,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 304, - 400, - 401, - 403, - 404, - 405, - 409, - 410, - 412, - 419, - 423, - 500 - ], - "errorTexts": [ - "already-exists", - "bad-request", - "conflict", - "forbidden", - "gone", - "item-not-found", - "linked-groups-participants-resource-limit", - "locked", - "not-allowed", - "not-authorized", - "resource-constraint", - "resource-limit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -68939,8 +67857,7 @@ { "tag": "AcceptGroupAddResponseServerError", "moduleName": "WASmaxInGroupsAcceptGroupAddResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -68963,18 +67880,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -69243,8 +68148,7 @@ { "tag": "AcknowledgeGroupResponseClientError", "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorItemNotFound", @@ -69267,18 +68171,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 404, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -69467,8 +68359,7 @@ { "tag": "AcknowledgeGroupResponseServerError", "moduleName": "WASmaxInGroupsAcknowledgeGroupResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -69491,18 +68382,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -71111,8 +69990,7 @@ { "tag": "AddParticipantsResponseClientError", "moduleName": "WASmaxInGroupsAddParticipantsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -71170,28 +70048,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 405, - 416, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-allowed", - "not-authorized", - "parent-group-suspended", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -71672,8 +70528,7 @@ { "tag": "AddParticipantsResponseServerError", "moduleName": "WASmaxInGroupsAddParticipantsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -71696,18 +70551,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -72234,8 +71077,7 @@ { "tag": "CancelGroupMembershipRequestsResponseClientError", "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -72263,20 +71105,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 404, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "locked", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -72502,8 +71330,7 @@ { "tag": "CancelGroupMembershipRequestsResponseServerError", "moduleName": "WASmaxInGroupsCancelGroupMembershipRequestsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -72526,18 +71353,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -75499,32 +74314,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 403, - 404, - 405, - 406, - 412, - 416, - 419, - 429, - 500 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "linked-groups-participants-resource-limit", - "not-acceptable", - "not-allowed", - "parent-group-suspended", - "rate-overlimit", - "resource-constraint", - "resource-limit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -76103,8 +74892,7 @@ { "tag": "CreateResponseServerError", "moduleName": "WASmaxInGroupsCreateResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -76127,18 +74915,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -76893,8 +75669,7 @@ { "tag": "CreateSubGroupSuggestionResponseClientError", "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorForbidden", @@ -76937,26 +75712,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 403, - 404, - 405, - 406, - 419, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "not-acceptable", - "not-allowed", - "rate-overlimit", - "resource-limit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -77317,8 +76072,7 @@ { "tag": "CreateSubGroupSuggestionResponseServerError", "moduleName": "WASmaxInGroupsCreateSubGroupSuggestionResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -77341,18 +76095,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -77621,8 +76363,7 @@ { "tag": "DeleteParentGroupResponseClientError", "moduleName": "WASmaxInGroupsDeleteParentGroupResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -77640,16 +76381,6 @@ "text": "item-not-found" } ], - "errorCodes": [ - 400, - 401, - 404 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-authorized" - ], "assertions": [ { "kind": "tag", @@ -77811,8 +76542,7 @@ { "tag": "DeleteParentGroupResponseServerError", "moduleName": "WASmaxInGroupsDeleteParentGroupResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -77835,18 +76565,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -78224,32 +76942,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 304, - 400, - 401, - 403, - 404, - 405, - 406, - 409, - 419, - 426 - ], - "errorTexts": [ - "already-exists", - "bad-request", - "conflict", - "forbidden", - "item-not-found", - "not-acceptable", - "not-allowed", - "not-authorized", - "resource-limit", - "upgrade-required" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -78721,8 +77413,7 @@ { "tag": "JoinLinkedGroupResponseServerError", "moduleName": "WASmaxInGroupsJoinLinkedGroupResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -78745,18 +77436,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -79649,8 +78328,7 @@ { "tag": "LinkSubGroupsResponseClientError", "moduleName": "WASmaxInGroupsLinkSubGroupsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -79703,30 +78381,6 @@ "text": "rate-overlimit" } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 405, - 413, - 416, - 419, - 429, - 433 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "not-allowed", - "not-authorized", - "parent-group-suspended", - "payload-too-large", - "pending-add-requests", - "rate-overlimit", - "resource-limit" - ], "assertions": [ { "kind": "tag", @@ -80147,8 +78801,7 @@ { "tag": "LinkSubGroupsResponseServerError", "moduleName": "WASmaxInGroupsLinkSubGroupsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -80171,18 +78824,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -81446,8 +80087,7 @@ { "tag": "MembershipRequestsActionResponseClientError", "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -81480,22 +80120,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 404, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "locked", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -81758,8 +80382,7 @@ { "tag": "MembershipRequestsActionResponseServerError", "moduleName": "WASmaxInGroupsMembershipRequestsActionResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -81782,18 +80405,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -82299,8 +80910,7 @@ { "tag": "PromoteDemoteAdminResponseClientError", "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorRateOverlimit", @@ -82343,26 +80953,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 423, - 429, - 486 - ], - "errorTexts": [ - "bad-request", - "blocked-integrity-enforcement", - "forbidden", - "item-not-found", - "locked", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -82699,8 +81289,7 @@ { "tag": "PromoteDemoteAdminResponseServerError", "moduleName": "WASmaxInGroupsPromoteDemoteAdminResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -82723,18 +81312,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -83362,8 +81939,7 @@ { "tag": "PromoteDemoteResponseClientError", "moduleName": "WASmaxInGroupsPromoteDemoteResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorRateOverlimit", @@ -83406,26 +81982,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 423, - 429, - 486 - ], - "errorTexts": [ - "bad-request", - "blocked-integrity-enforcement", - "forbidden", - "item-not-found", - "locked", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -83762,8 +82318,7 @@ { "tag": "PromoteDemoteResponseServerError", "moduleName": "WASmaxInGroupsPromoteDemoteResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -83786,18 +82341,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -84480,8 +83023,7 @@ { "tag": "RemoveParticipantsResponseClientError", "moduleName": "WASmaxInGroupsRemoveParticipantsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorRateOverlimit", @@ -84524,26 +83066,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 413, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-authorized", - "payload-too-large", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -84880,8 +83402,7 @@ { "tag": "RemoveParticipantsResponseServerError", "moduleName": "WASmaxInGroupsRemoveParticipantsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -84904,18 +83425,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -85197,8 +83706,7 @@ { "tag": "ReportMessagesResponseClientError", "moduleName": "WASmaxInGroupsReportMessagesResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -85226,20 +83734,6 @@ "text": "locked" } ], - "errorCodes": [ - 400, - 403, - 404, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "rate-overlimit" - ], "assertions": [ { "kind": "tag", @@ -85475,8 +83969,7 @@ { "tag": "ReportMessagesResponseServerError", "moduleName": "WASmaxInGroupsReportMessagesResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -85499,18 +83992,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -85973,8 +84454,7 @@ { "tag": "RevokeRequestCodeResponseClientError", "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -86007,22 +84487,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 423 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-authorized" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -86285,8 +84749,7 @@ { "tag": "RevokeRequestCodeResponseServerError", "moduleName": "WASmaxInGroupsRevokeRequestCodeResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -86309,18 +84772,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -86627,8 +85078,7 @@ { "tag": "SetDescriptionResponseClientError", "moduleName": "WASmaxInGroupsSetDescriptionResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -86671,26 +85121,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 406, - 409, - 423 - ], - "errorTexts": [ - "bad-request", - "conflict", - "forbidden", - "item-not-found", - "locked", - "not-acceptable", - "not-authorized" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -87051,8 +85481,7 @@ { "tag": "SetDescriptionResponseServerError", "moduleName": "WASmaxInGroupsSetDescriptionResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -87075,18 +85504,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -87839,8 +86256,7 @@ { "tag": "SetPropertyResponseClientError", "moduleName": "WASmaxInGroupsSetPropertyResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -87893,30 +86309,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 405, - 419, - 423, - 426, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-allowed", - "not-authorized", - "rate-overlimit", - "resource-limit", - "upgrade-required" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -88327,8 +86719,7 @@ { "tag": "SetPropertyResponseServerError", "moduleName": "WASmaxInGroupsSetPropertyResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -88351,18 +86742,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -88631,8 +87010,7 @@ { "tag": "SetSubjectResponseClientError", "moduleName": "WASmaxInGroupsSetSubjectResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -88675,26 +87053,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 403, - 404, - 406, - 423, - 429 - ], - "errorTexts": [ - "bad-request", - "forbidden", - "item-not-found", - "locked", - "not-acceptable", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -89055,8 +87413,7 @@ { "tag": "SetSubjectResponseServerError", "moduleName": "WASmaxInGroupsSetSubjectResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -89079,18 +87436,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -90301,8 +88646,7 @@ { "tag": "SubGroupSuggestionsActionResponseClientError", "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -90330,20 +88674,6 @@ "codeMax": 499 } ], - "errorCodes": [ - 400, - 401, - 404, - 429 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-authorized", - "rate-overlimit" - ], - "errorCodeMin": 400, - "errorCodeMax": 499, "assertions": [ { "kind": "tag", @@ -90569,8 +88899,7 @@ { "tag": "SubGroupSuggestionsActionResponseServerError", "moduleName": "WASmaxInGroupsSubGroupSuggestionsActionResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -90593,18 +88922,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -91281,8 +89598,7 @@ { "tag": "UnlinkGroupsResponseClientError", "moduleName": "WASmaxInGroupsUnlinkGroupsResponseClientError", - "kind": "error", - "errorClass": "client", + "kind": "client_error", "errorArms": [ { "name": "IQErrorBadRequest", @@ -91305,18 +89621,6 @@ "text": "not-acceptable" } ], - "errorCodes": [ - 400, - 401, - 404, - 406 - ], - "errorTexts": [ - "bad-request", - "item-not-found", - "not-acceptable", - "not-authorized" - ], "assertions": [ { "kind": "tag", @@ -91539,8 +89843,7 @@ { "tag": "UnlinkGroupsResponseServerError", "moduleName": "WASmaxInGroupsUnlinkGroupsResponseServerError", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "name": "IQErrorInternalServerError", @@ -91563,18 +89866,6 @@ "codeMax": 599 } ], - "errorCodes": [ - 500, - 503, - 530 - ], - "errorTexts": [ - "internal-server-error", - "partial-server-error", - "service-unavailable" - ], - "errorCodeMin": 500, - "errorCodeMax": 599, "assertions": [ { "kind": "tag", @@ -97130,24 +95421,6 @@ "text": "service-unavailable" } ], - "errorCodes": [ - 400, - 401, - 404, - 429, - 500, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "internal-server-error", - "item-not-found", - "not-authorized", - "rate-overlimit", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -97722,16 +95995,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 406, - 501 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "not-acceptable" - ], "assertions": [ { "kind": "tag", @@ -97917,20 +96180,13 @@ { "tag": "SendBufferResponseErrorRetry", "moduleName": "WASmaxInStatsSendBufferResponseErrorRetry", - "kind": "error", - "errorClass": "server", + "kind": "server_error", "errorArms": [ { "code": 503, "text": "service-unavailable" } ], - "errorCodes": [ - 503 - ], - "errorTexts": [ - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -98440,34 +96696,6 @@ "text": "wf-state-mismatch" } ], - "errorCodes": [ - 400, - 401, - 405, - 408, - 409, - 429, - 480, - 483, - 484, - 485, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "not-authorized", - "payload-enc-dec-failed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-not-found", - "wf-state-mismatch", - "wf-suspended" - ], "assertions": [ { "kind": "tag", @@ -99124,22 +97352,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 405, - 408, - 429, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "internal-server-error", - "not-allowed", - "rate-overlimit", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -99586,28 +97798,6 @@ "text": "wf-state-mismatch" } ], - "errorCodes": [ - 400, - 405, - 408, - 409, - 429, - 483, - 485, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-not-found", - "wf-state-mismatch" - ], "assertions": [ { "kind": "tag", @@ -100315,36 +98505,6 @@ "text": "wf-not-authorized-invalid-password" } ], - "errorCodes": [ - 304, - 400, - 401, - 405, - 408, - 409, - 416, - 429, - 480, - 483, - 485, - 500, - 503 - ], - "errorTexts": [ - "already-exists", - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "not-authorized", - "payload-enc-dec-failed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-not-authorized-invalid-password", - "wf-not-found", - "wf-state-mismatch" - ], "assertions": [ { "kind": "tag", @@ -101187,32 +99347,6 @@ "text": "wf-jurisdiction-gating" } ], - "errorCodes": [ - 304, - 400, - 401, - 405, - 408, - 409, - 418, - 429, - 480, - 500, - 503 - ], - "errorTexts": [ - "already-exists", - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "not-authorized", - "payload-enc-dec-failed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-jurisdiction-gating" - ], "assertions": [ { "kind": "tag", @@ -102000,24 +100134,6 @@ "text": "feature-not-implemented" } ], - "errorCodes": [ - 400, - 405, - 408, - 429, - 500, - 501, - 503 - ], - "errorTexts": [ - "bad-request", - "feature-not-implemented", - "internal-server-error", - "not-allowed", - "rate-overlimit", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -102627,36 +100743,6 @@ "text": "wf-not-authorized-invalid-password" } ], - "errorCodes": [ - 400, - 401, - 405, - 408, - 409, - 416, - 429, - 480, - 483, - 484, - 485, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "not-authorized", - "payload-enc-dec-failed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-not-authorized-invalid-password", - "wf-not-found", - "wf-state-mismatch", - "wf-suspended" - ], "assertions": [ { "kind": "tag", @@ -103423,22 +101509,6 @@ "text": "not-allowed" } ], - "errorCodes": [ - 400, - 405, - 408, - 429, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "internal-server-error", - "not-allowed", - "rate-overlimit", - "request-timeout", - "service-unavailable" - ], "assertions": [ { "kind": "tag", @@ -103884,34 +101954,6 @@ "text": "wf-state-mismatch" } ], - "errorCodes": [ - 400, - 401, - 405, - 408, - 409, - 429, - 480, - 483, - 484, - 485, - 500, - 503 - ], - "errorTexts": [ - "bad-request", - "conflict", - "internal-server-error", - "not-allowed", - "not-authorized", - "payload-enc-dec-failed", - "rate-overlimit", - "request-timeout", - "service-unavailable", - "wf-not-found", - "wf-state-mismatch", - "wf-suspended" - ], "assertions": [ { "kind": "tag", diff --git a/generated/manifest.json b/generated/manifest.json index e049038..cbf16d5 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,7 +5,7 @@ "iq": { "candidateModules": 207, "constraints": { - "errorArms": 643, + "errorArms": 644, "errorTexts": 38, "fieldEnumRefs": 146, "fieldLiterals": 1782, @@ -61,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "f39fd6773d22bdcfe871093b62a5dd3759cff4f2b04729105deab21d2f4ac4de" + "sha256": "53b08756d574cee154f8a62517227ed2e556cd4cd34d5965acd52f37289e393b" }, "mex": { "file": "mex/index.json", diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index ab04c71..a6cd8e3 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -100,7 +100,7 @@ ] }, "ErrorArm": { - "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nA variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be\ndescribed by two independent lists — nothing would then rule out `400\nfeature-not-implemented`, a combination the parser rejects, so an emitter picking one\nvalue from each list produces an unparseable stanza. The pairing is the point.\n\nEvery field is optional because each is a genuine shape: an arm may pin an exact\n`(code, text)`, pin only the code and read the text freely\n(`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and\nrange-check the code (the `IQErrorFallback*` arms).", "type": "object", "properties": { "code": { @@ -112,7 +112,7 @@ "format": "int64" }, "codeMax": { - "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "description": "Inclusive upper bound of the same range (see [`code_min`]).\n\n[`code_min`]: ErrorArm::code_min", "type": [ "integer", "null" @@ -120,7 +120,7 @@ "format": "int64" }, "codeMin": { - "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "description": "Inclusive lower bound of the codes this arm accepts, when it range-checks instead\nof pinning.", "type": [ "integer", "null" @@ -135,7 +135,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins. Absent is a fact, not a gap — see the note above.", "type": [ "string", "null" @@ -143,21 +143,6 @@ } } }, - "ErrorClass": { - "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", - "oneOf": [ - { - "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", - "type": "string", - "const": "client" - }, - { - "description": "The server failed (5xx) — the same request may succeed later.", - "type": "string", - "const": "server" - } - ] - }, "IncomingDef": { "description": "One received stanza's read-shape: which tag it parses, the module that defines the\nparser, and the recovered field tree.", "type": "object", @@ -523,49 +508,14 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`]; this is the only representation of the vocabulary,\ndeliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced\nlet an emitter combine one arm's code with another's text and produce a stanza no\nbranch accepts.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope**.\n\nTo answer \"does this RPC accept code N / text T?\", scan the arms — the question\nthe flat lists used to answer is one `.iter().any(…)` away, and asking it that way\ncannot produce an invalid pair.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" } }, - "errorClass": { - "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", - "anyOf": [ - { - "$ref": "#/$defs/ErrorClass" - }, - { - "type": "null" - } - ] - }, - "errorCodeMax": { - "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodeMin": { - "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, "errorEnvelope": { - "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: `` — the\npartial-failure envelope, shared by every arm — wrapping an inner `` whose\ntext the disjunction pins and whose code is range-checked. Pins are partitioned by\nthe node they are read from, so the envelope holds only the enclosing node's and\nthe inner node's shared constraints stay on the arms; mixing them would tell a\nconsumer that code 207 must also fall in 400–599.", "anyOf": [ { "$ref": "#/$defs/ErrorArm" @@ -575,13 +525,6 @@ } ] }, - "errorTexts": { - "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", - "type": "array", - "items": { - "type": "string" - } - }, "fields": { "type": "array", "items": { @@ -608,15 +551,29 @@ ] }, "ResponseVariantKind": { - "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).", + "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).\n\nThe error side is split by **who is at fault**, because that decides what a caller\nshould do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the\ntwo as separate parsers with disjoint code ranges (`bad-request` 400,\n`rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback\n500–599), so the split is the protocol's, not an interpretation.\n\nThe side is derived from the **codes** the variant accepts, never from its name: WA\nspells the arms inconsistently (`…ResponseServerError`, but also\n`…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a\nsuccess while asserting `type=\"error\"`). A variant whose codes are mixed or outside\nboth families stays [`Error`] — an error whose side could not be determined, which is\na fact worth stating rather than a coin flip.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ { "type": "string", "enum": [ - "success", - "error" + "success" ] }, + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client_error" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server_error" + }, + { + "description": "An error whose side the accepted codes do not determine.", + "type": "string", + "const": "error" + }, { "description": "A structured non-happy outcome that still parses (Nack, Conflict, …).", "type": "string", diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index ac09ace..ecc35f7 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -105,7 +105,7 @@ ] }, "ErrorArm": { - "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nA variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be\ndescribed by two independent lists — nothing would then rule out `400\nfeature-not-implemented`, a combination the parser rejects, so an emitter picking one\nvalue from each list produces an unparseable stanza. The pairing is the point.\n\nEvery field is optional because each is a genuine shape: an arm may pin an exact\n`(code, text)`, pin only the code and read the text freely\n(`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and\nrange-check the code (the `IQErrorFallback*` arms).", "type": "object", "properties": { "code": { @@ -117,7 +117,7 @@ "format": "int64" }, "codeMax": { - "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "description": "Inclusive upper bound of the same range (see [`code_min`]).\n\n[`code_min`]: ErrorArm::code_min", "type": [ "integer", "null" @@ -125,7 +125,7 @@ "format": "int64" }, "codeMin": { - "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "description": "Inclusive lower bound of the codes this arm accepts, when it range-checks instead\nof pinning.", "type": [ "integer", "null" @@ -140,7 +140,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins. Absent is a fact, not a gap — see the note above.", "type": [ "string", "null" @@ -148,21 +148,6 @@ } } }, - "ErrorClass": { - "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", - "oneOf": [ - { - "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", - "type": "string", - "const": "client" - }, - { - "description": "The server failed (5xx) — the same request may succeed later.", - "type": "string", - "const": "server" - } - ] - }, "IqRequestDef": { "description": "The outgoing request half of an IQ operation.", "type": "object", @@ -588,49 +573,14 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`]; this is the only representation of the vocabulary,\ndeliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced\nlet an emitter combine one arm's code with another's text and produce a stanza no\nbranch accepts.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope**.\n\nTo answer \"does this RPC accept code N / text T?\", scan the arms — the question\nthe flat lists used to answer is one `.iter().any(…)` away, and asking it that way\ncannot produce an invalid pair.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" } }, - "errorClass": { - "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", - "anyOf": [ - { - "$ref": "#/$defs/ErrorClass" - }, - { - "type": "null" - } - ] - }, - "errorCodeMax": { - "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodeMin": { - "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, "errorEnvelope": { - "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: `` — the\npartial-failure envelope, shared by every arm — wrapping an inner `` whose\ntext the disjunction pins and whose code is range-checked. Pins are partitioned by\nthe node they are read from, so the envelope holds only the enclosing node's and\nthe inner node's shared constraints stay on the arms; mixing them would tell a\nconsumer that code 207 must also fall in 400–599.", "anyOf": [ { "$ref": "#/$defs/ErrorArm" @@ -640,13 +590,6 @@ } ] }, - "errorTexts": { - "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", - "type": "array", - "items": { - "type": "string" - } - }, "fields": { "type": "array", "items": { @@ -673,15 +616,29 @@ ] }, "ResponseVariantKind": { - "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).", + "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).\n\nThe error side is split by **who is at fault**, because that decides what a caller\nshould do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the\ntwo as separate parsers with disjoint code ranges (`bad-request` 400,\n`rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback\n500–599), so the split is the protocol's, not an interpretation.\n\nThe side is derived from the **codes** the variant accepts, never from its name: WA\nspells the arms inconsistently (`…ResponseServerError`, but also\n`…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a\nsuccess while asserting `type=\"error\"`). A variant whose codes are mixed or outside\nboth families stays [`Error`] — an error whose side could not be determined, which is\na fact worth stating rather than a coin flip.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ { "type": "string", "enum": [ - "success", - "error" + "success" ] }, + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client_error" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server_error" + }, + { + "description": "An error whose side the accepted codes do not determine.", + "type": "string", + "const": "error" + }, { "description": "A structured non-happy outcome that still parses (Nack, Conflict, …).", "type": "string", diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 4fdde27..a06168a 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -116,7 +116,7 @@ ] }, "ErrorArm": { - "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nA variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be\ndescribed by two independent lists — nothing would then rule out `400\nfeature-not-implemented`, a combination the parser rejects, so an emitter picking one\nvalue from each list produces an unparseable stanza. The pairing is the point.\n\nEvery field is optional because each is a genuine shape: an arm may pin an exact\n`(code, text)`, pin only the code and read the text freely\n(`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and\nrange-check the code (the `IQErrorFallback*` arms).", "type": "object", "properties": { "code": { @@ -128,7 +128,7 @@ "format": "int64" }, "codeMax": { - "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "description": "Inclusive upper bound of the same range (see [`code_min`]).\n\n[`code_min`]: ErrorArm::code_min", "type": [ "integer", "null" @@ -136,7 +136,7 @@ "format": "int64" }, "codeMin": { - "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "description": "Inclusive lower bound of the codes this arm accepts, when it range-checks instead\nof pinning.", "type": [ "integer", "null" @@ -151,7 +151,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins. Absent is a fact, not a gap — see the note above.", "type": [ "string", "null" @@ -159,21 +159,6 @@ } } }, - "ErrorClass": { - "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", - "oneOf": [ - { - "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", - "type": "string", - "const": "client" - }, - { - "description": "The server failed (5xx) — the same request may succeed later.", - "type": "string", - "const": "server" - } - ] - }, "NotifActionChild": { "description": "A repeated sub-element an action arm maps over\n(`mapChildrenWithTag(\"participant\", …)`).", "type": "object", @@ -698,49 +683,14 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`]; this is the only representation of the vocabulary,\ndeliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced\nlet an emitter combine one arm's code with another's text and produce a stanza no\nbranch accepts.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope**.\n\nTo answer \"does this RPC accept code N / text T?\", scan the arms — the question\nthe flat lists used to answer is one `.iter().any(…)` away, and asking it that way\ncannot produce an invalid pair.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" } }, - "errorClass": { - "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", - "anyOf": [ - { - "$ref": "#/$defs/ErrorClass" - }, - { - "type": "null" - } - ] - }, - "errorCodeMax": { - "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodeMin": { - "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, "errorEnvelope": { - "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: `` — the\npartial-failure envelope, shared by every arm — wrapping an inner `` whose\ntext the disjunction pins and whose code is range-checked. Pins are partitioned by\nthe node they are read from, so the envelope holds only the enclosing node's and\nthe inner node's shared constraints stay on the arms; mixing them would tell a\nconsumer that code 207 must also fall in 400–599.", "anyOf": [ { "$ref": "#/$defs/ErrorArm" @@ -750,13 +700,6 @@ } ] }, - "errorTexts": { - "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", - "type": "array", - "items": { - "type": "string" - } - }, "fields": { "type": "array", "items": { @@ -783,15 +726,29 @@ ] }, "ResponseVariantKind": { - "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).", + "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).\n\nThe error side is split by **who is at fault**, because that decides what a caller\nshould do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the\ntwo as separate parsers with disjoint code ranges (`bad-request` 400,\n`rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback\n500–599), so the split is the protocol's, not an interpretation.\n\nThe side is derived from the **codes** the variant accepts, never from its name: WA\nspells the arms inconsistently (`…ResponseServerError`, but also\n`…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a\nsuccess while asserting `type=\"error\"`). A variant whose codes are mixed or outside\nboth families stays [`Error`] — an error whose side could not be determined, which is\na fact worth stating rather than a coin flip.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ { "type": "string", "enum": [ - "success", - "error" + "success" ] }, + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client_error" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server_error" + }, + { + "description": "An error whose side the accepted codes do not determine.", + "type": "string", + "const": "error" + }, { "description": "A structured non-happy outcome that still parses (Nack, Conflict, …).", "type": "string", diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 4c08e7e..5188131 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -100,7 +100,7 @@ ] }, "ErrorArm": { - "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nA variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be\ndescribed by two independent lists — nothing would then rule out `400\nfeature-not-implemented`, a combination the parser rejects, so an emitter picking one\nvalue from each list produces an unparseable stanza. The pairing is the point.\n\nEvery field is optional because each is a genuine shape: an arm may pin an exact\n`(code, text)`, pin only the code and read the text freely\n(`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and\nrange-check the code (the `IQErrorFallback*` arms).", "type": "object", "properties": { "code": { @@ -112,7 +112,7 @@ "format": "int64" }, "codeMax": { - "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "description": "Inclusive upper bound of the same range (see [`code_min`]).\n\n[`code_min`]: ErrorArm::code_min", "type": [ "integer", "null" @@ -120,7 +120,7 @@ "format": "int64" }, "codeMin": { - "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "description": "Inclusive lower bound of the codes this arm accepts, when it range-checks instead\nof pinning.", "type": [ "integer", "null" @@ -135,7 +135,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins. Absent is a fact, not a gap — see the note above.", "type": [ "string", "null" @@ -143,21 +143,6 @@ } } }, - "ErrorClass": { - "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", - "oneOf": [ - { - "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", - "type": "string", - "const": "client" - }, - { - "description": "The server failed (5xx) — the same request may succeed later.", - "type": "string", - "const": "server" - } - ] - }, "ParsedField": { "description": "One field extracted from a response stanza by a parser.\n\n`method` is the parser accessor used (e.g. `attrString`, `maybeChild`,\n`forEachChildWithTag`) and is left open-ended (free-form string) to track the\nreal WA accessor surface; the codegen switches on it.", "type": "object", @@ -490,49 +475,14 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`]; this is the only representation of the vocabulary,\ndeliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced\nlet an emitter combine one arm's code with another's text and produce a stanza no\nbranch accepts.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope**.\n\nTo answer \"does this RPC accept code N / text T?\", scan the arms — the question\nthe flat lists used to answer is one `.iter().any(…)` away, and asking it that way\ncannot produce an invalid pair.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" } }, - "errorClass": { - "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", - "anyOf": [ - { - "$ref": "#/$defs/ErrorClass" - }, - { - "type": "null" - } - ] - }, - "errorCodeMax": { - "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodeMin": { - "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, "errorEnvelope": { - "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: `` — the\npartial-failure envelope, shared by every arm — wrapping an inner `` whose\ntext the disjunction pins and whose code is range-checked. Pins are partitioned by\nthe node they are read from, so the envelope holds only the enclosing node's and\nthe inner node's shared constraints stay on the arms; mixing them would tell a\nconsumer that code 207 must also fall in 400–599.", "anyOf": [ { "$ref": "#/$defs/ErrorArm" @@ -542,13 +492,6 @@ } ] }, - "errorTexts": { - "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", - "type": "array", - "items": { - "type": "string" - } - }, "fields": { "type": "array", "items": { @@ -575,15 +518,29 @@ ] }, "ResponseVariantKind": { - "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).", + "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).\n\nThe error side is split by **who is at fault**, because that decides what a caller\nshould do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the\ntwo as separate parsers with disjoint code ranges (`bad-request` 400,\n`rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback\n500–599), so the split is the protocol's, not an interpretation.\n\nThe side is derived from the **codes** the variant accepts, never from its name: WA\nspells the arms inconsistently (`…ResponseServerError`, but also\n`…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a\nsuccess while asserting `type=\"error\"`). A variant whose codes are mixed or outside\nboth families stays [`Error`] — an error whose side could not be determined, which is\na fact worth stating rather than a coin flip.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ { "type": "string", "enum": [ - "success", - "error" + "success" ] }, + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client_error" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server_error" + }, + { + "description": "An error whose side the accepted codes do not determine.", + "type": "string", + "const": "error" + }, { "description": "A structured non-happy outcome that still parses (Nack, Conflict, …).", "type": "string", diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index 685436b..9674712 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -107,7 +107,7 @@ ] }, "ErrorArm": { - "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nThe flattened [`ResponseVariant::error_codes`] / [`error_texts`] lists answer \"what\nvalues does this RPC accept?\", but they cannot say which value goes with which: an\narm taking `400 bad-request` and `501 feature-not-implemented` flattens to two codes\nand two texts, and nothing then rules out `400 feature-not-implemented` — a\ncombination the parser rejects. An emitter that picks one value from each list\ntherefore produces an unparseable stanza, which is the exact failure class this\ndomain exists to prevent. The pairing lives here.\n\n[`error_texts`]: ResponseVariant::error_texts", + "description": "One accepted `` shape of a response variant: the `code` and `text` that must\noccur **together**.\n\nA variant accepting `400 bad-request` and `501 feature-not-implemented` cannot be\ndescribed by two independent lists — nothing would then rule out `400\nfeature-not-implemented`, a combination the parser rejects, so an emitter picking one\nvalue from each list produces an unparseable stanza. The pairing is the point.\n\nEvery field is optional because each is a genuine shape: an arm may pin an exact\n`(code, text)`, pin only the code and read the text freely\n(`IQErrorReportTokenValidationFail` accepts any text with 548), or pin neither and\nrange-check the code (the `IQErrorFallback*` arms).", "type": "object", "properties": { "code": { @@ -119,7 +119,7 @@ "format": "int64" }, "codeMax": { - "description": "Inclusive upper bound of a fallback arm's accepted code range.", + "description": "Inclusive upper bound of the same range (see [`code_min`]).\n\n[`code_min`]: ErrorArm::code_min", "type": [ "integer", "null" @@ -127,7 +127,7 @@ "format": "int64" }, "codeMin": { - "description": "Inclusive lower bound of a fallback arm's accepted code range.", + "description": "Inclusive lower bound of the codes this arm accepts, when it range-checks instead\nof pinning.", "type": [ "integer", "null" @@ -142,7 +142,7 @@ ] }, "text": { - "description": "The exact `text` this arm pins, when it pins one.\n\nAbsent is a **fact, not a gap**, and it comes in two shapes: a fallback arm pins\nneither value and accepts any text within [`code_min`]..=[`code_max`], while a\ncode-only arm pins the code and reads the text freely — `IQErrorReportTokenValidationFail`\npins `code=548` and accepts whatever `text` the server sends with it. In both, an\nemitter is free to choose the text; it is only [`code`] that is constrained.\n\n[`code`]: ErrorArm::code\n[`code_min`]: ErrorArm::code_min\n[`code_max`]: ErrorArm::code_max", + "description": "The exact `text` this arm pins. Absent is a fact, not a gap — see the note above.", "type": [ "string", "null" @@ -150,21 +150,6 @@ } } }, - "ErrorClass": { - "description": "Which side an error variant blames, for a variant whose [`ResponseVariantKind`] is\n[`Error`]. WA models the two as separate parsers with disjoint code ranges: a client\nerror carries a 4xx code (`bad-request` 400, `rate-overlimit` 429, fallback 400–499)\nand a server error a 5xx one (`internal-server-error` 500, fallback 500–599).\n\nEmitted alongside — not instead of — [`ResponseVariant::kind`], so a consumer that\nonly understands `success`/`error` keeps working.\n\n[`Error`]: ResponseVariantKind::Error", - "oneOf": [ - { - "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", - "type": "string", - "const": "client" - }, - { - "description": "The server failed (5xx) — the same request may succeed later.", - "type": "string", - "const": "server" - } - ] - }, "ParsedField": { "description": "One field extracted from a response stanza by a parser.\n\n`method` is the parser accessor used (e.g. `attrString`, `maybeChild`,\n`forEachChildWithTag`) and is left open-ended (free-form string) to track the\nreal WA accessor surface; the codegen switches on it.", "type": "object", @@ -497,49 +482,14 @@ } }, "errorArms": { - "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`] for why the flattened lists below cannot express it.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope** — an arm alone is not enough to build a response for such a\nvariant.\n\n[`error_envelope`]: ResponseVariant::error_envelope", + "description": "The accepted `` shapes, in parser order — **which code goes with which\ntext**. See [`ErrorArm`]; this is the only representation of the vocabulary,\ndeliberately, because the flattened `errorCodes` / `errorTexts` lists it replaced\nlet an emitter combine one arm's code with another's text and produce a stanza no\nbranch accepts.\n\nAn arm describes the *discriminating* `` node. When the variant also pins\nthe node above it, those pins are in [`error_envelope`], and the full shape is\n**arm + envelope**.\n\nTo answer \"does this RPC accept code N / text T?\", scan the arms — the question\nthe flat lists used to answer is one `.iter().any(…)` away, and asking it that way\ncannot produce an invalid pair.\n\n[`error_envelope`]: ResponseVariant::error_envelope", "type": "array", "items": { "$ref": "#/$defs/ErrorArm" } }, - "errorClass": { - "description": "For an error variant, whether it is the client-error (4xx) or server-error (5xx)\narm. Absent for a success variant, and for an error variant whose parser carries\nno recoverable code evidence.", - "anyOf": [ - { - "$ref": "#/$defs/ErrorClass" - }, - { - "type": "null" - } - ] - }, - "errorCodeMax": { - "description": "Upper bound of the fallback arm's code range (see [`error_code_min`]).\n\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodeMin": { - "description": "Lower bound of the open-ended fallback arm's code range, when the variant has one\n(`IQErrorFallbackServer` accepts any `code` in 500–599 with any `text`; its\nclient twin accepts 400–499). Together with [`error_code_max`] this says \"any\ncode in this range is also accepted\", which [`error_codes`] alone cannot express.\n\n[`error_code_max`]: ResponseVariant::error_code_max\n[`error_codes`]: ResponseVariant::error_codes", - "type": [ - "integer", - "null" - ], - "format": "int64" - }, - "errorCodes": { - "description": "The **closed set** of `` values this variant accepts, ascending — the\nunion over [`error_arms`], for the \"does this RPC accept code N?\" question.\n\nThe vocabulary is per-RPC, not global: `BatchGetGroupInfo`'s client-error arm\ntakes only `400` and `429` and **rejects `404`**, even though an\n`IQErrorItemNotFoundMixin` exists and other RPCs use it. Answering with a code\noutside this set matches no branch, so the client reports a parse failure rather\nthan the error. Empty when the variant pins no exact code (see [`error_code_min`]).\n\nAn emitter must pick a **pair** from [`error_arms`] (plus [`error_envelope`] when\npresent), never one value from this list and another from [`error_texts`].\n\n[`error_envelope`]: ResponseVariant::error_envelope\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_texts`]: ResponseVariant::error_texts\n[`error_code_min`]: ResponseVariant::error_code_min", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - }, "errorEnvelope": { - "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: the response carries\n`` — the partial-failure envelope, shared by every arm — around\nan inner `` whose text the disjunction pins. The envelope belongs to no\nsingle arm, so duplicating it into all of them would misreport where the code\nsits; it is recorded once here instead. Absent for the ordinary one-level error,\nwhich is nearly all of them.", + "description": "Pins on the `` node **enclosing** the one the arms discriminate, for a\ntwo-level error.\n\n`SetResponsePreKeySuccessVnameFailure` is the shape: `` — the\npartial-failure envelope, shared by every arm — wrapping an inner `` whose\ntext the disjunction pins and whose code is range-checked. Pins are partitioned by\nthe node they are read from, so the envelope holds only the enclosing node's and\nthe inner node's shared constraints stay on the arms; mixing them would tell a\nconsumer that code 207 must also fall in 400–599.", "anyOf": [ { "$ref": "#/$defs/ErrorArm" @@ -549,13 +499,6 @@ } ] }, - "errorTexts": { - "description": "The `` values this variant accepts, sorted — the union over\n[`error_arms`]. Empty when only a fallback (any-text) arm applies. See the warning\non [`error_codes`] about combining the two lists.\n\n[`error_arms`]: ResponseVariant::error_arms\n[`error_codes`]: ResponseVariant::error_codes", - "type": "array", - "items": { - "type": "string" - } - }, "fields": { "type": "array", "items": { @@ -582,15 +525,29 @@ ] }, "ResponseVariantKind": { - "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).", + "description": "How a response-root union variant classifies (drives codegen `Ok`/`Err` arms).\n\nThe error side is split by **who is at fault**, because that decides what a caller\nshould do: a 4xx will fail again if retried unchanged, a 5xx may not. WA models the\ntwo as separate parsers with disjoint code ranges (`bad-request` 400,\n`rate-overlimit` 429, fallback 400–499 / `internal-server-error` 500, fallback\n500–599), so the split is the protocol's, not an interpretation.\n\nThe side is derived from the **codes** the variant accepts, never from its name: WA\nspells the arms inconsistently (`…ResponseServerError`, but also\n`…ResponseInternalServerError`, and `SetResponsePreKeySuccessVnameFailure` reads as a\nsuccess while asserting `type=\"error\"`). A variant whose codes are mixed or outside\nboth families stays [`Error`] — an error whose side could not be determined, which is\na fact worth stating rather than a coin flip.\n\n[`Error`]: ResponseVariantKind::Error", "oneOf": [ { "type": "string", "enum": [ - "success", - "error" + "success" ] }, + { + "description": "The request was wrong (4xx) — retrying it unchanged will fail again.", + "type": "string", + "const": "client_error" + }, + { + "description": "The server failed (5xx) — the same request may succeed later.", + "type": "string", + "const": "server_error" + }, + { + "description": "An error whose side the accepted codes do not determine.", + "type": "string", + "const": "error" + }, { "description": "A structured non-happy outcome that still parses (Nack, Conflict, …).", "type": "string", From 058a959dc4fb1fd03f63f76ddc946b3d2aa7fb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 20:29:24 -0300 Subject: [PATCH 11/37] review: place presence-flag pins on their node, and stop three ambiguities guessing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five findings on 5830753. All applied; two were live in the committed IR. A presence flag was losing the node its attribute is read from. `hasListCDhash` carried the `c_dhash` echo with no `sourcePath` while its sibling `` fields correctly had one, so a consumer would apply the rule to the `` root rather than to ``. The underlying binding's path is now copied along with the pin — four fields fixed. A direct error parser that reads `code`/`text` without pinning either published no arm at all, making "this variant accepts an unrestricted error" indistinguishable from "we extracted no vocabulary". `CreateCustomPaymentMethodResponseIQErrorWithCodeAndReason` and `RemoveCustomPaymentMethodResponseError` are that shape; both now carry an empty unnamed arm. The two variants still without arms (`RequestSilentNonceResponseRecoveryRequired`, `CreateResponseGroupAlreadyExists`) read no error fields at all, which is the honest empty. Three ambiguities that were resolving by guess rather than refusing: - The scope for a return was built as a pre-pass over the whole statement list, so `var x = attr("a"); if (c) return {a:x}; var x = attr("b")` reported the first branch as reading `b` — an assignment that has not run. Hoisting moves the declaration, not the assignment. Bindings now accumulate in statement order and each return snapshots what is in scope where it sits. - `factory_params` cannot tell the exports binding from the loader or the dependency map (WA spells the factory `(t,n,r,o,a,i,l)` and uses `l` in some modules, `i` in others), so restricting receivers to "any factory parameter" still let `moduleArg.GROUP_ACTIONS = wrong` win over the real export. A property assigned differently through two parameters is now refused outright — a missing action instead of a wire tag read from an unrelated table. - The enum drop counter keyed by enum identity, so ten fields losing the same enum counted as one and nine of them vanishing moved nothing. The key now carries the attribute the accessor reads, making the unit "distinct lost constraints" as documented rather than "distinct enums". 10 -> 33, which is the honest number. Determinism holds, schemas validate, codegen byte-identical, no --allow-shrink. --- crates/wa-notif/src/actions.rs | 64 ++++++++++++++++++---------- crates/wa-notif/src/tests.rs | 32 +++++++++++++- crates/wa-scan/src/response_index.rs | 15 ++++++- crates/wa-scan/src/response_smax.rs | 36 ++++++++++------ generated/iq/index.json | 18 ++++++++ generated/manifest.json | 6 +-- 6 files changed, 131 insertions(+), 40 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index a8edc3c..08dd177 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -149,7 +149,7 @@ fn const_string_map(slice: &str, export: &str) -> Option { exports: HashMap::new(), }; collector.visit_program(&ret.program); - if let Some(direct) = collector.exports.get(export) { + if let Some(Some(direct)) = collector.exports.get(export) { return match direct { Export::Inline(map) => Some(map.clone()), Export::Local(name) => collector.locals.get(name).cloned().flatten(), @@ -158,6 +158,7 @@ fn const_string_map(slice: &str, export: &str) -> Option { None } +#[derive(PartialEq)] enum Export { Inline(ConstMap), Local(String), @@ -204,7 +205,9 @@ struct ConstCollector { /// or `actionType` values. Ambiguity therefore resolves to nothing, which shows up /// as a missing action rather than a silently wrong one. locals: HashMap>, - exports: HashMap, + /// `None` marks a property assigned differently through two factory parameters — + /// unresolvable rather than guessed. + exports: HashMap>, } impl<'a> Visit<'a> for ConstCollector { @@ -234,14 +237,26 @@ impl<'a> Visit<'a> for ConstCollector { self.receivers.is_empty() || self.receivers.iter().any(|r| r == id.name.as_str()) }) { - if let Some(map) = string_const_object(&a.right) { - self.exports - .entry(prop.to_string()) - .or_insert(Export::Inline(map)); - } else if let Some(id) = as_identifier(&a.right) { - self.exports - .entry(prop.to_string()) - .or_insert(Export::Local(id.to_string())); + // `factory_params` cannot tell the exports binding from the loader or the + // dependency map — WA spells the factory `(t,n,r,o,a,i,l)` and uses `l` in + // some modules and `i` in others. So rather than guess which parameter is + // the real one, a property assigned DIFFERENTLY through two of them is + // refused: the export resolves to nothing, and the failure is a missing + // action rather than a wire tag read out of an unrelated table. + let found = string_const_object(&a.right) + .map(Export::Inline) + .or_else(|| as_identifier(&a.right).map(|id| Export::Local(id.to_string()))); + if let Some(export) = found { + match self.exports.entry(prop.to_string()) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(Some(export)); + } + std::collections::hash_map::Entry::Occupied(mut e) => { + if e.get().as_ref() != Some(&export) { + e.insert(None); + } + } + } } } walk::walk_assignment_expression(self, a); @@ -526,22 +541,27 @@ fn collect_returns<'b, 'a>( outer: &Scope<'b, 'a>, out: &mut Vec>, ) { - // This level's declarations, layered over the enclosing ones. Built per branch on - // purpose: mutually exclusive branches routinely rebind the same minified name to - // DIFFERENT accessors, and a single flattened scope would make one branch's return - // read the other branch's attribute — a wrong `wireName`, not a missing field. + // Bindings accumulate in STATEMENT ORDER and each return snapshots what is in scope + // where it sits. A pre-pass over the whole list would install a later initializer + // before an earlier return: `var x = attr("a"); if (c) return {a:x}; var x = + // attr("b")` would report the first branch as reading `b`, an assignment that has + // not run. Hoisting moves the declaration, not the assignment. + // + // Scopes are also per branch, because mutually exclusive branches routinely rebind + // the same minified name to different accessors, and one flattened scope would make + // one branch's return read the other branch's attribute — a wrong `wireName` rather + // than a missing field. let mut scope = outer.clone(); for s in stmts { - if let Statement::VariableDeclaration(decl) = s { - for d in &decl.declarations { - if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) { - scope.insert(name.as_str(), init); + match s { + Statement::VariableDeclaration(decl) => { + for d in &decl.declarations { + if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) + { + scope.insert(name.as_str(), init); + } } } - } - } - for s in stmts { - match s { Statement::ReturnStatement(r) => { if let Some(arg) = r.argument.as_ref() { let mut branches = Vec::new(); diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index f879b5a..72e094e 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -637,8 +637,12 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:t.attrInt("expiration"), code:t.attrString("code")||"none"}, w(t)); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NOT_EPHEMERAL: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.EPHEMERAL, duration:0}; - case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MODIFY: - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.MODIFY, entries:q(t)}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MODIFY: { + var z = t.attrString("first"); + if (t.hasChild("early")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.MODIFY, entries:q(t), pick:z}; + var z = t.attrString("second"); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, pick:z}; + } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { var n; return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0}; @@ -1015,3 +1019,27 @@ fn a_branching_mapped_child_callback_weakens_its_branch_only_fields() { assert!(f("id").expect("id").required, "read by every branch"); assert!(!f("extra").expect("extra").required, "read by one branch"); } + +#[test] +fn a_return_sees_only_the_bindings_that_ran_before_it() { + // `var z = attr("first"); if (c) return {pick:z}; var z = attr("second"); return + // {pick:z}` — hoisting moves the declaration, not the assignment, so the early return + // reads `first`. A scope built as a pre-pass over the whole statement list installs + // the later initializer first and reports both returns as reading `second`. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let pick = |action: &str| { + notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "modify") + .find(|a| a.action_type.as_deref() == Some(action)) + .and_then(|a| a.fields.iter().find(|f| f.name == "pick")) + .map(|f| f.wire_name.as_str()) + }; + assert_eq!( + pick("modify"), + Some("first"), + "the early return ran before the rebind" + ); + assert_eq!(pick("restrict"), Some("second")); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index e9bd72b..1149099 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -356,8 +356,13 @@ fn collect_error_arms(fields: &[ParsedField], out: &mut Vec) -> bool { collect_union_arms(fields, out, &mut found_union); if !found_union { // No disjunction: the variant parses one `` shape, so its pins are one arm. + // An arm with NO pins is still information when the parser reads `code`/`text` at + // all — it says "one accepted shape, unconstrained", which is a different claim + // from "no vocabulary extracted". `CreateCustomPaymentMethodResponseIQErrorWithCodeAndReason` + // and `RemoveCustomPaymentMethodResponseError` are that shape, and dropping their + // arm made an unrestricted error indistinguishable from a failed extraction. let arm = arm_pins(fields); - if arm != ErrorArm::default() { + if arm != ErrorArm::default() || reads_error_fields(fields) { out.push(arm); } } @@ -400,6 +405,14 @@ fn collect_union_arms(fields: &[ParsedField], out: &mut Vec, found: &m } } +/// Whether the parser reads an `` `code`/`text` at all, pinned or not. +fn reads_error_fields(fields: &[ParsedField]) -> bool { + fields.iter().any(|f| { + matches!(f.wire_name.as_deref().unwrap_or(&f.name), "code" | "text") + || f.children.as_deref().is_some_and(reads_error_fields) + }) +} + /// The `code`/`text` pins of one error shape, scanning its whole field subtree but not /// descending into disjunction alternatives (each of those is its own arm). fn arm_pins(fields: &[ParsedField]) -> ErrorArm { diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index f15741c..6e9f805 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -153,7 +153,7 @@ impl<'a> Resolver<'a> { /// attribute identically. `None` — never a guess — when the module isn't in the bundle, /// the export isn't a resolvable enum, or any variant value isn't a string (every /// stanza-attr enum is a wire-token enum). - fn resolve_enum(&self, module: &str, name: &str) -> Option { + fn resolve_enum(&self, module: &str, name: &str, occurrence: &str) -> Option { let key = (module.to_string(), name.to_string()); // Count per OCCURRENCE, not per distinct enum: `dropsByReason` measures how much // constraint data was lost, and every other reason here is per-occurrence. A @@ -161,7 +161,7 @@ impl<'a> Resolver<'a> { // the same unresolvable enum report N losses rather than one. if let Some(hit) = self.enum_cache.borrow().get(&key) { if hit.is_none() { - self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}")); + self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}@{occurrence}")); } return hit.clone(); } @@ -188,7 +188,7 @@ impl<'a> Resolver<'a> { }) }); if resolved.is_none() { - self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}")); + self.drop_note_keyed(ENUM_DROP, format!("{module}.{name}@{occurrence}")); } self.enum_cache.borrow_mut().insert(key, resolved.clone()); resolved @@ -963,21 +963,21 @@ fn enum_arg_ref( // is an identifier / `X.value`, the attr a string, and — in the `optional(ACC, …)` // form — the leading accessor ref is itself `o("WASmaxParseUtils").attrStringEnum`, // excluded by requiring a non-`WASmaxParse*` owner module). + // The wire attribute the accessor reads, as the occurrence discriminator: two fields + // validating against the same unresolvable enum are two lost constraints. + let occurrence = args + .iter() + .filter_map(arg_expr) + .find_map(as_string_lit) + .unwrap_or(""); let Some((module, name)) = args.iter().filter_map(arg_expr).find_map(module_member_ref) else { // An inline enum object, a local alias, or a `WASmaxParse*`-owned reference: the // accessor validates against SOMETHING we could not name. That is the exact // "a constraint existed and we lost it" case the counter exists for. - resolver.drop_note_keyed( - ENUM_DROP, - args.iter() - .filter_map(arg_expr) - .find_map(as_string_lit) - .unwrap_or("") - .to_string(), - ); + resolver.drop_note_keyed(ENUM_DROP, format!("@{occurrence}")); return None; }; - resolver.resolve_enum(&module, &name) + resolver.resolve_enum(&module, &name, occurrence) } /// `o("Mod").NAME` (a member reference, not a call) → `(Mod, NAME)`, excluding the @@ -1478,6 +1478,10 @@ fn collect_object_fields( required: true, literal_value: underlying.and_then(binding_literal_value), reference_path: underlying.and_then(binding_reference_path), + // The flag reports on an attribute of whatever node the underlying + // accessor read — ``, not the `` root. Dropping the + // path sends a consumer to apply the echo one level too high. + source_path: underlying.and_then(binding_source_path), ..Default::default() }); continue; @@ -1807,6 +1811,14 @@ fn binding_wire_name(b: &Binding) -> Option { } } +/// The wrapper tags a binding descends before reading, when it reads off a child node. +fn binding_source_path(b: &Binding) -> Option> { + match b { + Binding::Field { source_path, .. } => source_path.clone(), + _ => None, + } +} + /// The constant a binding pins its value to, when it is a `literal`/`optionalLiteral`. fn binding_literal_value(b: &Binding) -> Option { match b { diff --git a/generated/iq/index.json b/generated/iq/index.json index d1e4859..133de21 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -2534,6 +2534,9 @@ "referencePath": [ "item", "dhash" + ], + "sourcePath": [ + "list" ] }, { @@ -2653,6 +2656,9 @@ "referencePath": [ "item", "dhash" + ], + "sourcePath": [ + "list" ] }, { @@ -2866,6 +2872,9 @@ "referencePath": [ "item", "dhash" + ], + "sourcePath": [ + "list" ] }, { @@ -33408,6 +33417,9 @@ "referencePath": [ "item", "dhash" + ], + "sourcePath": [ + "list" ] }, { @@ -90705,6 +90717,9 @@ "tag": "CreateCustomPaymentMethodResponseIQErrorWithCodeAndReason", "moduleName": "WASmaxInBrPaymentCreateCustomPaymentMethodResponseIQErrorWithCodeAndReason", "kind": "error", + "errorArms": [ + {} + ], "assertions": [ { "kind": "tag", @@ -94819,6 +94834,9 @@ "tag": "RemoveCustomPaymentMethodResponseError", "moduleName": "WASmaxInBrPaymentRemoveCustomPaymentMethodResponseError", "kind": "error", + "errorArms": [ + {} + ], "assertions": [ { "kind": "tag", diff --git a/generated/manifest.json b/generated/manifest.json index cbf16d5..9b0c9f2 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,7 +5,7 @@ "iq": { "candidateModules": 207, "constraints": { - "errorArms": 644, + "errorArms": 646, "errorTexts": 38, "fieldEnumRefs": 146, "fieldLiterals": 1782, @@ -21,7 +21,7 @@ "dropsByReason": { "iq builder substring present but no AST iq call": 5, "mixin fragment (folded into requests, not a standalone stanza)": 66, - "response enum argument not structurally resolvable": 10 + "response enum argument not structurally resolvable": 33 }, "excludedFragments": 66, "stanzas": 143, @@ -61,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "53b08756d574cee154f8a62517227ed2e556cd4cd34d5965acd52f37289e393b" + "sha256": "722709bdb6994e533b9eb8db37a35d8ee4dbe5d993b22bb64de945beaab08847" }, "mex": { "file": "mex/index.json", From 632eb30d964afc9cbc0869231c793151a5cacda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 20:40:35 -0300 Subject: [PATCH 12/37] wap: derive `maybe*` accessor types instead of enumerating them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folding in the deferred follow-up, and finishing it rather than fixing the one symptom. The reported item — `attrPhoneUserJid` / `maybeAttrPhoneUserJid` typed as `string` instead of `user_jid` — was already fixed in bd585b6, when review found it independently and measuring showed the blast radius was nil. But the reason it happened is that `method_field_type` enumerates every spelling by hand, and enumeration misses cases: a `maybe` variant that nobody remembered to add defaults to `string`, silently, for a JID flavour the module's own doc calls protocol-safety-critical. So `maybeAttrX` now derives from `attrX` rather than sitting beside it. Listing a flavour once covers both spellings, and one that is never listed defaults visibly for both instead of only for the half someone forgot. Swept the bundle for the accessors WA's parsers actually use (48 distinct) and ran every one through the classifier. Eight were still falling to `string` for no good reason, each confirmed against its definition in the bundle rather than guessed from its name: attrFutureTime -> integer (reads attrInt, then range-checks) contentUint -> integer (decodes an int out of contentBytes) contentEnum -> enum (reads contentString, then looks up) contentBytesRange -> bytes contentLiteralBytes -> bytes maybeAttrGroupJid -> group_jid (delegates to attrGroupJid) attrFromJidChat -> jid_typed (delegates to attrJidWithType) attrFromJidPhoneChat -> jid_typed (same) Committed output is unchanged: none of the eight currently appears as a `method` in any domain, so this is the class of bug closed rather than an instance repaired. The IR is byte-identical. Two tests pin it: one asserts every `maybe` spelling agrees with its base (and that the PN/LID split survives the derivation), the other pins the classification of every bundle accessor, so a new one has to be judged instead of defaulting quietly. The accessors still resolving to `string` are correct: `attrString`, `contentString`, `attrStanzaId`, and the reference helpers, whose type comes from the accessor they wrap. --- crates/wa-ir/src/wap.rs | 130 +++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 14 deletions(-) diff --git a/crates/wa-ir/src/wap.rs b/crates/wa-ir/src/wap.rs index 44e81e9..3954450 100644 --- a/crates/wa-ir/src/wap.rs +++ b/crates/wa-ir/src/wap.rs @@ -105,26 +105,42 @@ pub fn is_optional_method(m: &str) -> bool { } /// The scalar [`ParsedFieldType`] a response accessor decodes to. +/// +/// `maybeAttrX` is derived from `attrX` rather than enumerated beside it. The two decode +/// identically — the `maybe` spelling only tolerates absence — so listing both invites +/// exactly the bug that motivated this: `maybeAttrPhoneUserJid` was missing from the +/// table and reported a PN user JID as a bare `string`, the conflation this module's own +/// note calls protocol-safety-critical. Deriving means a flavour added to the table +/// covers both spellings, and one that is never added defaults visibly for both. pub fn method_field_type(m: &str) -> ParsedFieldType { + if let Some(rest) = m.strip_prefix("maybe") + && let Some(first) = rest.chars().next() + { + let attr: String = first.to_lowercase().chain(rest.chars().skip(1)).collect(); + return method_field_type(&attr); + } match m { - ATTR_STRING | MAYBE_ATTR_STRING => ParsedFieldType::String, + ATTR_STRING => ParsedFieldType::String, // An enum accessor decodes to an enum, not a bare string. The smax side already // types these `Enum` through its own normalizer, so mapping them to `String` here // made one concept two types across domains — 486 fields said `enum` and 28 said // `string` for the same accessors — and left an `enumRef` hanging off a field a // consumer filtering on `type == "enum"` would never look at. - ATTR_ENUM | MAYBE_ATTR_ENUM | ATTR_ENUM_VALUES => ParsedFieldType::Enum, - ATTR_INT | MAYBE_ATTR_INT | ATTR_TIME | MAYBE_ATTR_TIME => ParsedFieldType::Integer, + ATTR_ENUM | ATTR_ENUM_VALUES => ParsedFieldType::Enum, + // `attrFutureTime` range-checks a unix time it read with `attrInt`; `contentUint` + // decodes an unsigned integer out of the content bytes. + ATTR_INT | ATTR_TIME | "attrIntRange" | "attrFutureTime" | CONTENT_INT | "contentUint" => { + ParsedFieldType::Integer + } // Each JID accessor pins the flavor (server/format) it validates. Keeping the // flavor — rather than collapsing to a bare `Jid` — is protocol-safety-critical: // a LID user JID and a PN user JID are different identities for the same person. - ATTR_USER_JID | MAYBE_ATTR_USER_JID => ParsedFieldType::UserJid, + ATTR_USER_JID => ParsedFieldType::UserJid, // The `phone*` spellings are the explicit-PN aliases of the plain user/device - // accessors. Missing them typed a PN user JID as a bare `string`, which is - // exactly the conflation the note above calls protocol-safety-critical. - "attrPhoneUserJid" | "maybeAttrPhoneUserJid" => ParsedFieldType::UserJid, + // accessors. + "attrPhoneUserJid" => ParsedFieldType::UserJid, "attrPhoneDeviceJid" => ParsedFieldType::DeviceJid, - ATTR_LID_USER_JID | MAYBE_ATTR_LID_USER_JID => ParsedFieldType::LidUserJid, + ATTR_LID_USER_JID => ParsedFieldType::LidUserJid, ATTR_DEVICE_JID => ParsedFieldType::DeviceJid, ATTR_LID_DEVICE_JID => ParsedFieldType::LidDeviceJid, ATTR_GROUP_JID => ParsedFieldType::GroupJid, @@ -132,7 +148,10 @@ pub fn method_field_type(m: &str) -> ParsedFieldType { ATTR_CALL_JID => ParsedFieldType::CallJid, ATTR_BROADCAST_JID => ParsedFieldType::BroadcastJid, ATTR_STATUS_JID => ParsedFieldType::StatusJid, - ATTR_JID_WITH_TYPE | "attrJidEnum" => ParsedFieldType::JidTyped, + // `attrFromJidChat` / `attrFromJidPhoneChat` both delegate to `attrJidWithType`. + ATTR_JID_WITH_TYPE | "attrJidEnum" | "attrFromJidChat" | "attrFromJidPhoneChat" => { + ParsedFieldType::JidTyped + } // `attrWapJid`/`attrChatJid`/`attrFromJid` accept more than one flavor // (a chat is a user or a group), so they stay a generic `Jid`. ATTR_WAP_JID | ATTR_CHAT_JID | ATTR_FROM_JID => ParsedFieldType::Jid, @@ -141,11 +160,13 @@ pub fn method_field_type(m: &str) -> ParsedFieldType { "attrPhoneChatJid" | "attrDomainJid" | "attrLidJid" | "attrFromPhoneJid" => { ParsedFieldType::Jid } - // Range-checked integers and the enum accessors, whose raw spellings the legacy - // parsers use directly. - "attrIntRange" | "contentInt" => ParsedFieldType::Integer, - "attrStringEnum" | "contentStringEnum" | "attrEnumOrNullIfUnknown" => ParsedFieldType::Enum, - CONTENT_BYTES => ParsedFieldType::Bytes, + // The enum accessors, whose raw spellings the legacy parsers use directly. + // `contentEnum` reads the content as a string and looks it up. + "attrStringEnum" | "contentStringEnum" | "contentEnum" | "attrEnumOrNullIfUnknown" => { + ParsedFieldType::Enum + } + // Every content accessor that yields raw bytes, whatever length rule it applies. + CONTENT_BYTES | "contentBytesRange" | "contentLiteralBytes" => ParsedFieldType::Bytes, _ => ParsedFieldType::String, } } @@ -214,6 +235,87 @@ mod tests { } } + #[test] + fn maybe_spellings_inherit_their_base_accessor() { + // The rule that replaced enumerating both spellings: a `maybe` variant decodes + // exactly like its base, so a flavour listed once covers both — and a flavour + // never listed defaults visibly for both instead of only for the `maybe` half, + // which is how `maybeAttrPhoneUserJid` came to report a PN user JID as a string. + for base in [ + ATTR_STRING, + ATTR_INT, + ATTR_ENUM, + ATTR_TIME, + ATTR_USER_JID, + ATTR_LID_USER_JID, + ATTR_GROUP_JID, + ATTR_DEVICE_JID, + ATTR_NEWSLETTER_JID, + "attrPhoneUserJid", + ] { + let maybe = format!("maybe{}{}", base[..1].to_uppercase(), &base[1..]); + assert_eq!( + method_field_type(&maybe), + method_field_type(base), + "{maybe} must decode like {base}" + ); + } + // The PN/LID split survives the derivation — the distinction this module calls + // protocol-safety-critical. + assert_eq!( + method_field_type("maybeAttrPhoneUserJid"), + ParsedFieldType::UserJid + ); + assert_eq!( + method_field_type(MAYBE_ATTR_LID_USER_JID), + ParsedFieldType::LidUserJid + ); + assert_ne!( + method_field_type("maybeAttrPhoneUserJid"), + method_field_type(MAYBE_ATTR_LID_USER_JID) + ); + } + + #[test] + fn every_bundle_accessor_is_classified() { + // The accessors WA's parsers actually use, swept from the bundle. Anything here + // that falls through to `String` is either genuinely a string or a flavour we are + // silently dropping — so the list is pinned, and a new one must be judged rather + // than defaulting quietly. + for (m, want) in [ + ("attrFutureTime", ParsedFieldType::Integer), + ("attrIntRange", ParsedFieldType::Integer), + ("contentUint", ParsedFieldType::Integer), + ("contentEnum", ParsedFieldType::Enum), + ("attrStringEnum", ParsedFieldType::Enum), + ("contentStringEnum", ParsedFieldType::Enum), + ("attrEnumOrNullIfUnknown", ParsedFieldType::Enum), + ("contentBytesRange", ParsedFieldType::Bytes), + ("contentLiteralBytes", ParsedFieldType::Bytes), + ("maybeAttrGroupJid", ParsedFieldType::GroupJid), + ("attrPhoneDeviceJid", ParsedFieldType::DeviceJid), + ("attrFromJidChat", ParsedFieldType::JidTyped), + ("attrFromJidPhoneChat", ParsedFieldType::JidTyped), + ("attrJidEnum", ParsedFieldType::JidTyped), + ("attrDomainJid", ParsedFieldType::Jid), + ("attrLidJid", ParsedFieldType::Jid), + ("attrPhoneChatJid", ParsedFieldType::Jid), + ("attrFromPhoneJid", ParsedFieldType::Jid), + ] { + assert_eq!(method_field_type(m), want, "{m}"); + } + // Genuinely strings: a stanza id, and the reference helpers whose type comes from + // the accessor they wrap. + for m in [ + ATTR_STRING, + CONTENT_STRING, + "attrStanzaId", + "attrStringFromReference", + ] { + assert_eq!(method_field_type(m), ParsedFieldType::String, "{m}"); + } + } + #[test] fn optional_and_field_types() { assert!(is_optional_method(MAYBE_ATTR_ENUM)); From 90cd7d66c85a5d09e2e25b51078ce85b04f555c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 20:40:57 -0300 Subject: [PATCH 13/37] docs: note the accessor-type classification in the constraints section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fcebc99..e59e9f5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Field shapes tell you how to *read* a stanza. They are not enough to *produce* o - **Pinned values** (`literalValue` on a field) — `type="admin"` on a successful promote, `matched="true"`/`"false"` on a blocklist update, `code=429` on a rate-limit error. `required` separates the two forms: a required pin is a hard discriminator (must be present and exact); an optional one is pinned only when present (may be omitted, must never be contradicted). - **The per-RPC error vocabulary** (`errorArms`, plus `errorEnvelope` for a two-level error) — a **closed** set, and it differs per RPC: `BatchGetGroupInfo` accepts `400 bad-request` and `429 rate-overlimit` and **rejects `404 item-not-found`**, even though that mixin exists and other RPCs use it. Each arm pairs the `code` with the `text` that must accompany it, so an emitter cannot combine one arm's code with another's text and produce a stanza no branch accepts; an arm that range-checks instead of pinning carries `codeMin`/`codeMax`. The variant's `kind` says which side is at fault (`client_error` / `server_error`), derived from the codes rather than from the parser's name. - **Response enums** (`enumRef` on a field) — the legal values behind an `attrStringEnum`, resolved the same way the request side already resolves them, instead of a bare `"type": "enum"`. +- **The accessor's decoded type**, kept faithful: every `attr*` / `content*` spelling WA's parsers use is classified (a `maybeAttrX` derives from `attrX`, so a flavour cannot be covered for one spelling and missed for the other), and the JID flavours stay distinct — a PN user JID and a LID user JID are different identities for the same person and must never collapse into one `string`. - **Notification action unions** (`notifications[].actions`) — the payload inside the envelope. The `wireTag → actionType` mapping is **many-to-one** (`not_ephemeral` normalises into `ephemeral` with `duration: 0`, so branching on `not_ephemeral` is dead code) and field names are rebound (the disappearing-message timer arrives in `expiration`, but the action field is `duration`). Neither is derivable from the wire. Note the contract version: this raised `schemaVersion` to **2.0.0**, and it is a real major bump rather than a cautious one. Three changes need action from a 1.x consumer: From accb9b913782256b4065b1088fe87e45cd6bef34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 20:59:09 -0300 Subject: [PATCH 14/37] review: refuse conflicting field sources, and key drops per parser site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four findings on 90cd7d6. All applied, all latent in the current bundle — `generated/` is byte-identical. One of them is a half-applied change of my own from the previous commit. I taught `method_field_type` about `contentUint` / `contentEnum` / `contentBytesRange` / `contentLiteralBytes`, and left the notification action reader whitelisting three content spellings — so those accessors would not be recognised as content reads at all and the whole field would drop, taking its enum or byte-range constraint with it. `wap` now owns the question (`is_content_method` covers every spelling) and the reader asks it instead of listing. Two were the "wrong, not missing" rule not being applied to merges: - Two branches binding the same output key to DIFFERENT wire reads kept the first branch's attribute, type and enum silently. There is no single answer there — `{who: p.attrString("jid")}` versus `{who: p.attrString("lid")}` is exactly the JID conflation this repo treats as protocol-safety-critical — so the key is dropped instead. - Two branches mapping the same child name discarded the later one whole, losing fields only it reads and leaving fields only the first reads marked required. They merge now, under the same requiredness rule as scalars; a same-name child with a different wire tag is still dropped, since that has no single answer either. And the enum drop counter is keyed per parser site. This is its fourth revision, so worth being plain about the sequence: CodeRabbit asked for per-occurrence, review then showed per-invocation inflates, I keyed by enum, then by enum plus attribute — and each time I wrote a doc claiming "distinct lost constraints" that the key did not deliver. It does now: the key carries a hash of the parser body, so two parsers losing the same `(enum, attribute)` pair are two losses while the two analysis passes over one body stay one. The count is unchanged at 33 — no site collisions exist today — but the number now means what it says, with a test that pins both halves. --- crates/wa-ir/src/wap.rs | 18 +++++- crates/wa-notif/src/actions.rs | 42 +++++++++++--- crates/wa-notif/src/tests.rs | 31 +++++++++- crates/wa-scan/src/response_smax.rs | 87 ++++++++++++++++++++++++----- 4 files changed, 151 insertions(+), 27 deletions(-) diff --git a/crates/wa-ir/src/wap.rs b/crates/wa-ir/src/wap.rs index 3954450..68d58f4 100644 --- a/crates/wa-ir/src/wap.rs +++ b/crates/wa-ir/src/wap.rs @@ -59,9 +59,23 @@ pub fn is_child_method(m: &str) -> bool { ) } -/// Content accessors (`contentString` / `contentBytes` / `contentInt`). +/// Content accessors — every spelling that reads the element's content rather than an +/// attribute, whatever it decodes to. The single source of truth for the question, so a +/// caller cannot recognise a subset: teaching [`method_field_type`] about `contentUint` +/// while a reader still whitelisted three spellings made those fields drop out entirely +/// instead of being typed. pub fn is_content_method(m: &str) -> bool { - matches!(m, CONTENT_STRING | CONTENT_BYTES | CONTENT_INT) + matches!( + m, + CONTENT_STRING + | CONTENT_BYTES + | CONTENT_INT + | "contentUint" + | "contentEnum" + | "contentStringEnum" + | "contentBytesRange" + | "contentLiteralBytes" + ) } /// The attribute value accessors (`attr*` / `maybeAttr*`, including the typed-JID diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 08dd177..239ebf4 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -488,8 +488,17 @@ fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { } } for c in from.children { - if !into.children.iter().any(|x| x.name == c.name) { - into.children.push(c); + match into.children.iter_mut().find(|x| x.name == c.name) { + // Two branches mapping the same output name: their element shapes are + // alternatives, so the field sets merge under the same requiredness rule. + // Discarding the later one lost fields only it reads, and left fields only + // the first reads marked required though a legal branch omits them. + Some(existing) if existing.wire_tag == c.wire_tag => { + merge_fields(&mut existing.fields, c.fields, false) + } + // Same output name, different wire tag — no single answer; drop it. + Some(_) => {} + None => into.children.push(c), } } into.constant_fields @@ -1155,12 +1164,11 @@ fn find_accessor_at<'b, 'a>( enum_arg, }); } - // `X.contentString()` / `X.contentInt()` — no argument; the wire name is the tag - // of whatever `X` descends to, and `""` when it reads the arm's own node. - if matches!( - method, - wap::CONTENT_STRING | wap::CONTENT_INT | wap::CONTENT_BYTES - ) { + // A content read — `X.contentString()`, `X.contentUint()`, … — takes no attribute + // name, so the wire name is the tag of whatever `X` descends to, and `""` when it + // reads the arm's own node. Asking `wap` rather than listing spellings here is + // what keeps a newly classified accessor from being silently dropped. + if wap::is_content_method(method) { // The receiver is often hoisted (`var body = child.child("body"); … body // .contentString()`), so it must be dereferenced through the scope first — // otherwise the wire name comes out empty and a consumer cannot tell content @@ -1285,6 +1293,11 @@ fn collect_shape_fields<'b, 'a>( /// Fold one branch's fields into the accumulated set: a field either branch reads is /// present, and required only when EVERY branch reads it unconditionally. +/// +/// When two branches bind the same output key to **different wire reads**, there is no +/// single answer — reporting the first branch's attribute would describe the other +/// branch's payload wrongly — so the key is dropped. Missing, not wrong, as everywhere +/// else in this module. fn merge_fields(into: &mut Vec, from: Vec, first: bool) { if !first { for existing in into.iter_mut() { @@ -1293,13 +1306,24 @@ fn merge_fields(into: &mut Vec, from: Vec, f } } } + let mut conflicting: Vec = Vec::new(); for f in from { match into.iter_mut().find(|x| x.name == f.name) { - Some(existing) => existing.required &= f.required, + Some(existing) if same_wire_read(existing, &f) => existing.required &= f.required, + Some(existing) => conflicting.push(existing.name.clone()), None => into.push(NotifActionField { required: f.required && first, ..f }), } } + into.retain(|f| !conflicting.contains(&f.name)); +} + +/// Whether two bindings of the same output key describe the same wire read. +fn same_wire_read(a: &NotifActionField, b: &NotifActionField) -> bool { + a.wire_name == b.wire_name + && a.field_type == b.field_type + && a.content == b.content + && a.enum_ref == b.enum_ref } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 72e094e..90afb8a 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -612,8 +612,8 @@ __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ function w(e){ if (e.hasChild("a")) return {alpha:e.attrString("alpha")}; return {beta:e.attrString("beta")}; } function q(e){ return e.mapChildrenWithTag("entry", function(x){ - if (x.hasChild("full")) return {id:x.attrString("id"), extra:x.attrString("extra")}; - return {id:x.attrString("id")}; + if (x.hasChild("full")) return {id:x.attrString("id"), extra:x.attrString("extra"), who:x.attrString("jid")}; + return {id:x.attrString("id"), who:x.attrString("lid")}; }); } function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; @@ -1043,3 +1043,30 @@ fn a_return_sees_only_the_bindings_that_ran_before_it() { ); assert_eq!(pick("restrict"), Some("second")); } + +#[test] +fn a_key_bound_to_different_wire_reads_is_dropped_not_guessed() { + // Two branches binding `who` to different attributes (`jid` and `lid`) have no single + // answer — publishing the first branch'"'"'s attribute would describe the other branch'"'"'s + // payload wrongly, and this is the JID conflation the repo treats as + // protocol-safety-critical. The key is dropped instead. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let entries = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "modify") + .expect("modify arm") + .children + .iter() + .find(|c| c.name == "entries") + .expect("entries child"); + let names: Vec<&str> = entries.fields.iter().map(|f| f.name.as_str()).collect(); + assert!( + !names.contains(&"who"), + "a conflicting source must be refused, not resolved to the first branch: {names:?}" + ); + // The unambiguous keys still merge normally. + let f = |n: &str| entries.fields.iter().find(|f| f.name == n); + assert!(f("id").expect("id").required, "read by every branch"); + assert!(!f("extra").expect("extra").required, "read by one branch"); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 6e9f805..98649b3 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -416,7 +416,22 @@ fn analyze_fn_source( Statement::FunctionDeclaration(f) => Some(&**f), _ => None, })?; - analyze_function(func, locals, resolver, visited) + analyze_function(func, locals, resolver, visited, &parser_site(fn_source)) +} + +/// A stable identity for one parser body, for keying diagnostics per SITE. +/// +/// The source itself, hashed: two parsers validating the same attribute against the same +/// unresolvable enum are two lost constraints, and keying on `(enum, attribute)` alone +/// collapsed them — so removing one moved no counter. Hashing the body rather than +/// threading a module/function name keeps the key stable across the two analysis passes +/// (`resolve` for fields, `assertions` for discriminators re-walk the identical source) +/// without carrying context through every frame. +fn parser_site(fn_source: &str) -> String { + use std::hash::{Hash, Hasher}; + let mut h = std::collections::hash_map::DefaultHasher::new(); + fn_source.hash(&mut h); + format!("{:016x}", h.finish()) } fn analyze_function( @@ -424,6 +439,7 @@ fn analyze_function( locals: &LocalFns, resolver: &Resolver, visited: &mut HashSet, + site: &str, ) -> Option<(Vec, Vec)> { let body = func.body.as_ref()?; // A response parser's signature is `parse…(node, reference)`: the second parameter @@ -439,6 +455,7 @@ fn analyze_function( locals, resolver, reference_param: reference_param.as_deref(), + site, }; // Same-node cross-module mixins bubble their discriminators up by default (so an // error variant inherits the `type:"error"` its mixin asserts). But a mixin whose @@ -500,6 +517,9 @@ struct FnCtx<'c> { resolver: &'c Resolver<'c>, /// The parser's `reference` parameter — the request an echo rule is relative to. reference_param: Option<&'c str>, + /// This parser body's identity, so a dropped constraint is counted per site rather + /// than per constraint name — see [`parser_site`]. + site: &'c str, } /// Classify the RHS of a railway binding into a [`Binding`], recording any @@ -517,6 +537,7 @@ fn classify_call( let FnCtx { resolver, reference_param, + site, .. } = *ctx; let Some(call) = as_call(init) else { @@ -643,7 +664,7 @@ fn classify_call( } (None, None) => resolver.drop_note_keyed( "literal attr value not statically resolvable", - attr.to_string(), + format!("{site}:{attr}"), ), // A resolved pin on a descended node: carried on the field below, // where it belongs, rather than as a root assertion. @@ -722,7 +743,7 @@ fn classify_call( if literal_value.is_none() && reference_path.is_none() { resolver.drop_note_keyed( "optionalLiteral attr value not statically resolvable", - wire_name.clone().unwrap_or_default(), + format!("{site}:{}", wire_name.clone().unwrap_or_default()), ); } // The wrapped accessor decides the type, exactly as in the `literal` arm. @@ -762,7 +783,7 @@ fn classify_call( | "attrFromReference" | "optionalAttrFromReference" | "contentStringFromReference" => { - classify_reference(method, args, resolver, reference_param) + classify_reference(method, args, resolver, reference_param, site) } // `optional(ACCESSOR, node, …)` → the wrapped accessor decides the type; // required = false. @@ -785,7 +806,7 @@ fn classify_call( wire_name, source_path, literal_value: None, - enum_ref: enum_arg_ref(inner, args, resolver), + enum_ref: enum_arg_ref(inner, args, resolver, site), reference_path: None, } } @@ -816,7 +837,7 @@ fn classify_call( wire_name, source_path, literal_value: None, - enum_ref: enum_arg_ref(Some(other), args, resolver), + enum_ref: enum_arg_ref(Some(other), args, resolver, site), reference_path: None, } } @@ -872,6 +893,7 @@ fn classify_reference( args: &[Argument], resolver: &Resolver, reference_param: Option<&str>, + site: &str, ) -> Binding { let Some(path_idx) = args .iter() @@ -879,7 +901,7 @@ fn classify_reference( else { resolver.drop_note_keyed( "reference path argument not statically resolvable", - method.to_string(), + format!("{site}:{method}"), ); return Binding::None; }; @@ -899,7 +921,7 @@ fn classify_reference( if node.is_none() || node != reference_param { resolver.drop_note_keyed( "reference read from a node other than the request", - format!("{method}:{}", path.join("/")), + format!("{site}:{method}:{}", path.join("/")), ); return Binding::None; } @@ -955,6 +977,7 @@ fn enum_arg_ref( accessor: Option<&str>, args: &[Argument], resolver: &Resolver, + site: &str, ) -> Option { if !matches!(accessor, Some("attrStringEnum") | Some("contentStringEnum")) { return None; @@ -965,11 +988,13 @@ fn enum_arg_ref( // excluded by requiring a non-`WASmaxParse*` owner module). // The wire attribute the accessor reads, as the occurrence discriminator: two fields // validating against the same unresolvable enum are two lost constraints. - let occurrence = args - .iter() - .filter_map(arg_expr) - .find_map(as_string_lit) - .unwrap_or(""); + let occurrence = format!( + "{site}:{}", + args.iter() + .filter_map(arg_expr) + .find_map(as_string_lit) + .unwrap_or("") + ); let Some((module, name)) = args.iter().filter_map(arg_expr).find_map(module_member_ref) else { // An inline enum object, a local alias, or a `WASmaxParse*`-owned reference: the // accessor validates against SOMETHING we could not name. That is the exact @@ -977,7 +1002,7 @@ fn enum_arg_ref( resolver.drop_note_keyed(ENUM_DROP, format!("@{occurrence}")); return None; }; - resolver.resolve_enum(&module, &name, occurrence) + resolver.resolve_enum(&module, &name, &occurrence) } /// `o("Mod").NAME` (a member reference, not a call) → `(Mod, NAME)`, excluding the @@ -2321,6 +2346,40 @@ mod tests { } } + #[test] + fn two_parsers_losing_the_same_enum_are_two_lost_constraints() { + // The counter's unit is distinct LOST CONSTRAINTS, so keying on the enum — or on + // the enum plus the attribute — collapses separate parsers reading the same thing + // and lets constraints disappear without moving the number. The parser body is + // part of the key; the two analysis passes over one body still count once. + let slices = HashMap::new(); + let resolver = Resolver::new(&slices); + let one = r#"function e(node){ + var s = o("WASmaxParseUtils").attrStringEnum(node, "state", o("Missing").ENUM_X); if(!s.success) return s; + return o("WAResultOrError").makeResult({ state: s.value }); + }"#; + let two = r#"function e(node){ + var t = o("WASmaxParseUtils").assertTag(node, "iq"); if(!t.success) return t; + var s = o("WASmaxParseUtils").attrStringEnum(node, "state", o("Missing").ENUM_X); if(!s.success) return s; + return o("WAResultOrError").makeResult({ state: s.value }); + }"#; + let count = || { + resolver + .drop_counts() + .get("response enum argument not structurally resolvable") + .copied() + .unwrap_or(0) + }; + analyze_fn_source(one, &LocalFns::new(), &resolver, &mut HashSet::new()); + assert_eq!(count(), 1); + // Re-analyzing the SAME body (as the assertion pass does) must not double-count. + analyze_fn_source(one, &LocalFns::new(), &resolver, &mut HashSet::new()); + assert_eq!(count(), 1, "the same site analyzed twice is one loss"); + // A DIFFERENT parser losing the same enum on the same attribute is a second loss. + analyze_fn_source(two, &LocalFns::new(), &resolver, &mut HashSet::new()); + assert_eq!(count(), 2, "distinct parser sites are distinct losses"); + } + #[test] fn unresolvable_enum_is_recorded_not_guessed() { // The enum module isn't in the bundle: no link, and a drop reason so a consumer From 768a453381212b556baa8adce342c75fb9ab17a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 21:22:49 -0300 Subject: [PATCH 15/37] wap: derive the attr-accessor predicate too, and give JID reads their real name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five findings on accb9b9. Four are extraction correctness; the fifth turned out to be a live contradiction inside the committed IR. `attrJidWithType` carried TWO types in the same artifact — `jid` 68 times and `jid_typed` 12 — because the smax normalizer collapses ten distinct JID accessors onto that one canonical name with a flat `Jid`, while the legacy path types the real accessor through the shared classifier. Same wire contract, different type depending on which scanner saw it, and the `JidTyped` doc names `attrJidEnum` explicitly as belonging to it. Each JID accessor now keeps its OWN name and takes its type from the one classifier, so the two cannot disagree by construction: 34 `attrJidEnum` reads become `jid_typed`, 31 `literalJid` and 3 `attrDomainJid` keep `jid` under their real names. That change caught a second enumerate-vs-derive bug on the way in. `is_attr_method` was a hand-written list, so accessors that stopped being called `attrJidWithType` fell out of it and the codegen silently dropped 85 lines. The predicate is now derived from the spelling — anything WA names `attr…` / `maybeAttr…` reads an attribute, `hasAttr` is the presence test, the `…FromReference` helpers read the request, and `literalJid` is an attribute read while `literalContent` is not. Deriving it also recovered two fields nobody had noticed were missing: `auth_ttl` and `ttl`, read with `attrFutureTime`, were dropped entirely because that spelling was absent from the list. The reference codegen gains them. The four extraction fixes, all latent: - The scalar branch merge updated only `required`, so two branches binding one key to different wire reads published the first branch's attribute — the same PN/LID conflation the mapped-child merge already refuses. It goes through the same rule now. - The conflict tombstone lived for one merge call, so with three or more branches `A(jid)` vs `B(lid)` removed the key and `C(jid)` added it back. It now spans the whole merge. - A child bound to one name with two different wire tags left the first in place, though the comment beside it said to drop it. Now both go. - A fall-through label that does setup first (`case A: log(); case B: return {…}`) was treated as its own empty arm, because the search stopped at the first non-empty consequent rather than the first that yields a shape. Determinism holds, schemas validate, the new-field A/B is still identical, and no --allow-shrink. --- crates/wa-ir/src/wap.rs | 45 +++----- crates/wa-notif/src/actions.rs | 77 +++++++++---- crates/wa-notif/src/tests.rs | 49 +++++++- crates/wa-scan/src/response_smax.rs | 14 +-- generated/iq/index.json | 172 +++++++++++++++------------- generated/manifest.json | 6 +- generated/notif/index.json | 12 +- generated/srvreq/index.json | 38 +++--- 8 files changed, 244 insertions(+), 169 deletions(-) diff --git a/crates/wa-ir/src/wap.rs b/crates/wa-ir/src/wap.rs index 68d58f4..21f63ee 100644 --- a/crates/wa-ir/src/wap.rs +++ b/crates/wa-ir/src/wap.rs @@ -84,33 +84,19 @@ pub fn is_content_method(m: &str) -> bool { /// is the single source of truth for "is this method an attribute accessor", /// shared by the scanner and the codegen so the two can't drift. pub fn is_attr_method(m: &str) -> bool { - matches!( - m, - ATTR_STRING - | ATTR_INT - | ATTR_ENUM - | ATTR_ENUM_VALUES - | MAYBE_ATTR_STRING - | MAYBE_ATTR_INT - | MAYBE_ATTR_ENUM - | ATTR_DEVICE_JID - | ATTR_GROUP_JID - | ATTR_JID_WITH_TYPE - | ATTR_TIME - | MAYBE_ATTR_TIME - | ATTR_USER_JID - | MAYBE_ATTR_USER_JID - | ATTR_WAP_JID - | ATTR_CHAT_JID - | ATTR_FROM_JID - | ATTR_LID_USER_JID - | MAYBE_ATTR_LID_USER_JID - | ATTR_LID_DEVICE_JID - | ATTR_NEWSLETTER_JID - | ATTR_CALL_JID - | ATTR_BROADCAST_JID - | ATTR_STATUS_JID - ) + // Derived from the spelling, not enumerated. Every accessor WA names `attr…` / + // `maybeAttr…` reads an attribute; `hasAttr` is the presence test, not a read, and + // the `…FromReference` helpers read off the REQUEST rather than the node. + // + // Enumerating was the bug: the list drifted from `method_field_type`, so giving a JID + // accessor its own name instead of collapsing it onto `attrJidWithType` silently + // dropped 85 lines of generated code — the fields fell outside the list and stopped + // being treated as attribute reads at all. + // `literalJid` is an attribute read too — it reads a JID attribute and pins it to a + // value — while `literalContent` pins the element's content and names no attribute. + let literal_attr = m.starts_with("literal") && !m.contains("Content"); + let attr = (m.starts_with("attr") || m.starts_with("maybeAttr")) && m != HAS_ATTR; + (attr || literal_attr) && !m.ends_with("FromReference") } /// `maybe*` accessors decode to optional / non-required fields. @@ -171,9 +157,8 @@ pub fn method_field_type(m: &str) -> ParsedFieldType { ATTR_WAP_JID | ATTR_CHAT_JID | ATTR_FROM_JID => ParsedFieldType::Jid, // Multi-flavor accessors: a chat is a user OR a group, `attrDomainJid`/`attrLidJid` // accept more than one server, so they stay a generic JID rather than a string. - "attrPhoneChatJid" | "attrDomainJid" | "attrLidJid" | "attrFromPhoneJid" => { - ParsedFieldType::Jid - } + "attrJid" | "literalJid" | "attrPhoneChatJid" | "attrDomainJid" | "attrLidJid" + | "attrFromPhoneJid" => ParsedFieldType::Jid, // The enum accessors, whose raw spellings the legacy parsers use directly. // `contentEnum` reads the content as a string and looks it up. "attrStringEnum" | "contentStringEnum" | "contentEnum" | "attrEnumOrNullIfUnknown" => { diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 239ebf4..5d27c49 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -415,14 +415,16 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec let Some(wire_tag) = case.test.as_ref().and_then(|t| ctx.consts.resolve(t)) else { continue; }; - // `case TAG_A: case TAG_B: return {…}` — a fall-through label has an empty body - // and runs the next non-empty one. Recording it as an empty action would lose the - // action type and every field of a legally dispatched tag. + // `case TAG_A: case TAG_B: return {…}` — a fall-through label runs the next case + // that actually produces something. Stopping at the first NON-EMPTY consequent is + // not enough: a label may do setup or log first (`case TAG_A: log();`) and still + // fall through, and treating that as its own arm loses the action type and every + // field of a legally dispatched tag. Search forward for the first case that + // yields a result shape instead. let consequent = switch.cases[i..] .iter() - .map(|c| &c.consequent) - .find(|c| !c.is_empty()) - .map(|c| &c[..]) + .map(|c| &c.consequent[..]) + .find(|c| !arm_result_shapes(c).is_empty()) .unwrap_or(&case.consequent); let shapes = arm_result_shapes(consequent); if shapes.is_empty() { @@ -478,15 +480,13 @@ fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { existing.required = false; } } - for f in from.fields { - match into.fields.iter_mut().find(|x| x.name == f.name) { - Some(existing) => existing.required &= f.required, - None => into.fields.push(NotifActionField { - required: false, - ..f - }), - } - } + // Scalars go through the same rule the mapped children use, tombstone included: a + // key bound to different wire reads in two branches has no single answer, and + // updating only `required` here would publish the first branch's attribute for both. + let mut dead = Conflicts::new(); + merge_fields(&mut into.fields, from.fields, false, &mut dead); + apply_conflicts(&mut into.fields, &dead); + let mut dead_children: Vec = Vec::new(); for c in from.children { match into.children.iter_mut().find(|x| x.name == c.name) { // Two branches mapping the same output name: their element shapes are @@ -494,13 +494,18 @@ fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { // Discarding the later one lost fields only it reads, and left fields only // the first reads marked required though a legal branch omits them. Some(existing) if existing.wire_tag == c.wire_tag => { - merge_fields(&mut existing.fields, c.fields, false) + let mut d = Conflicts::new(); + merge_fields(&mut existing.fields, c.fields, false, &mut d); + apply_conflicts(&mut existing.fields, &d); } - // Same output name, different wire tag — no single answer; drop it. - Some(_) => {} + // Same output name, different wire TAG — two different elements under one + // name. Leaving the first in place would tell a consumer every legal shape + // uses it, so both go. + Some(existing) => dead_children.push(existing.name.clone()), None => into.children.push(c), } } + into.children.retain(|c| !dead_children.contains(&c.name)); into.constant_fields .retain(|c| from.constant_fields.contains(c)); } @@ -1230,13 +1235,15 @@ fn collect_accessor_fields<'b, 'a>( // one list would mark a field read in only one branch as required, and let two // branches reusing an output name silently keep whichever came first. let mut merged: Vec = Vec::new(); + let mut dead = Conflicts::new(); let mut branches = 0usize; for (shape, inner) in fn_result_shapes(e, &scope) { branches += 1; let mut fields = Vec::new(); collect_shape_fields(shape, &inner, consts, &mut fields); - merge_fields(&mut merged, fields, branches == 1); + merge_fields(&mut merged, fields, branches == 1, &mut dead); } + apply_conflicts(&mut merged, &dead); // A callback that returns a bare value rather than an object // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the // field by, so the wire attribute names it — better than reporting no fields at all. @@ -1291,14 +1298,26 @@ fn collect_shape_fields<'b, 'a>( w.visit_expression(deref_ident(shape, scope)); } +/// Names whose wire read two branches disagreed on. A **tombstone**, kept for the whole +/// merge rather than one call of it: with three or more branches, `A(jid)` conflicting +/// with `B(lid)` would remove the key and then `C(jid)` — seeing nothing there — would +/// add it back, so the union would again advertise one source while another legal branch +/// reads a different attribute. +type Conflicts = std::collections::HashSet; + /// Fold one branch's fields into the accumulated set: a field either branch reads is /// present, and required only when EVERY branch reads it unconditionally. /// /// When two branches bind the same output key to **different wire reads**, there is no /// single answer — reporting the first branch's attribute would describe the other -/// branch's payload wrongly — so the key is dropped. Missing, not wrong, as everywhere -/// else in this module. -fn merge_fields(into: &mut Vec, from: Vec, first: bool) { +/// branch's payload wrongly — so the key is tombstoned. Missing, not wrong, as everywhere +/// else in this module. Call [`apply_conflicts`] once the last branch is in. +fn merge_fields( + into: &mut Vec, + from: Vec, + first: bool, + dead: &mut Conflicts, +) { if !first { for existing in into.iter_mut() { if !from.iter().any(|f| f.name == existing.name) { @@ -1306,18 +1325,26 @@ fn merge_fields(into: &mut Vec, from: Vec, f } } } - let mut conflicting: Vec = Vec::new(); for f in from { + if dead.contains(&f.name) { + continue; + } match into.iter_mut().find(|x| x.name == f.name) { Some(existing) if same_wire_read(existing, &f) => existing.required &= f.required, - Some(existing) => conflicting.push(existing.name.clone()), + Some(existing) => { + dead.insert(existing.name.clone()); + } None => into.push(NotifActionField { required: f.required && first, ..f }), } } - into.retain(|f| !conflicting.contains(&f.name)); +} + +/// Drop every tombstoned key. Run after the final branch, never between them. +fn apply_conflicts(fields: &mut Vec, dead: &Conflicts) { + fields.retain(|f| !dead.contains(&f.name)); } /// Whether two bindings of the same output key describe the same wire read. diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 90afb8a..21d8582 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -598,7 +598,7 @@ __d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o, __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ var cache={}; cache.GROUP_NOTIFICATION_TAG={ADD:"WRONG_add",SUBJECT:"WRONG_subject"}; - var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",GROWTH_LOCKED:"growth_locked",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ @@ -613,7 +613,8 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr function w(e){ if (e.hasChild("a")) return {alpha:e.attrString("alpha")}; return {beta:e.attrString("beta")}; } function q(e){ return e.mapChildrenWithTag("entry", function(x){ if (x.hasChild("full")) return {id:x.attrString("id"), extra:x.attrString("extra"), who:x.attrString("jid")}; - return {id:x.attrString("id"), who:x.attrString("lid")}; + if (x.hasChild("mid")) return {id:x.attrString("id"), who:x.attrString("lid")}; + return {id:x.attrString("id"), who:x.attrString("jid")}; }); } function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; @@ -643,6 +644,8 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr var z = t.attrString("second"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, pick:z}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.GROWTH_LOCKED: + o("WALogger").INFO("setup"); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { var n; return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0}; @@ -1070,3 +1073,45 @@ fn a_key_bound_to_different_wire_reads_is_dropped_not_guessed() { assert!(f("id").expect("id").required, "read by every branch"); assert!(!f("extra").expect("extra").required, "read by one branch"); } + +#[test] +fn a_fall_through_label_with_setup_still_inherits_the_shared_action() { + // `case GROWTH_LOCKED: log(); case SUSPENDED: return {…}` — the first label has a + // non-empty body that produces no shape and falls through. Treating "non-empty" as + // "its own arm" lost the action type and every field of a legally dispatched tag. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let arm = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "growth_locked") + .expect("growth_locked arm"); + assert_eq!( + arm.action_type.as_deref(), + Some("restrict"), + "must inherit the body it falls through into" + ); + assert!(!arm.constant_fields.is_empty(), "including its constants"); +} + +#[test] +fn a_third_branch_cannot_resurrect_a_conflicted_key() { + // A(jid) vs B(lid) tombstones `who`; without a tombstone that outlives the single + // merge, C(jid) sees nothing there and adds it back — and the union again advertises + // one source while another legal branch reads a different attribute. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let entries = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "modify") + .expect("modify arm") + .children + .iter() + .find(|c| c.name == "entries") + .expect("entries child"); + let names: Vec<&str> = entries.fields.iter().map(|f| f.name.as_str()).collect(); + assert!( + !names.contains(&"who"), + "the third branch must not resurrect the conflict: {names:?}" + ); + assert!(names.contains(&"id"), "unambiguous keys survive: {names:?}"); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 98649b3..706fedb 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -1221,13 +1221,13 @@ fn normalize_accessor(m: &str) -> Option<(String, ParsedFieldType, Option)> "attrCallJid" => s(wap::ATTR_CALL_JID, ParsedFieldType::CallJid), "attrBroadcastJid" => s(wap::ATTR_BROADCAST_JID, ParsedFieldType::BroadcastJid), "attrStatusJid" => s(wap::ATTR_STATUS_JID, ParsedFieldType::StatusJid), - // Generic / multi-flavor accessors stay a bare Jid: `attrChatJid` is a user OR - // a group, `attrJid`/`attrDomainJid` accept any, `attrJidEnum` pins the server - // kind via a runtime enum arg (no single static flavor). - "attrJid" | "attrDomainJid" | "attrChatJid" | "attrPhoneChatJid" | "attrWapJid" - | "attrFromJid" | "attrFromPhoneJid" | "attrLidJid" | "attrJidEnum" | "literalJid" => { - s(wap::ATTR_JID_WITH_TYPE, ParsedFieldType::Jid) - } + // Every remaining JID accessor keeps its OWN name and takes its type from the + // shared classifier. Collapsing them onto `attrJidWithType`/`Jid` lost which + // accessor validated the value AND made one method carry two types in the same + // artifact — `attrJidWithType` appeared as `jid` 68 times (this path) and + // `jid_typed` 12 times (the legacy path, via the classifier), for the same wire + // contract. One classifier, one answer. + other if wap::method_field_type(other).is_jid() => s(other, wap::method_field_type(other)), _ => None, } } diff --git a/generated/iq/index.json b/generated/iq/index.json index 133de21..bb78037 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -4812,10 +4812,10 @@ "tag": "user", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -5259,10 +5259,10 @@ "tag": "user", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -6046,10 +6046,10 @@ "tag": "user", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -6062,10 +6062,10 @@ "name": "FetchMissingPreKeysUserSuccess", "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -6467,10 +6467,10 @@ "tag": "user", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -6483,10 +6483,10 @@ "name": "FetchMissingPreKeysUserSuccess", "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -12524,10 +12524,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -20387,7 +20387,7 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -23146,7 +23146,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -26348,7 +26348,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -29016,7 +29016,7 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -29579,7 +29579,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -30585,7 +30585,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -33928,7 +33928,7 @@ "literalValue": "result" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -33968,7 +33968,7 @@ "literalValue": "result" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -34015,7 +34015,7 @@ "literalValue": "result" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -34055,7 +34055,7 @@ "literalValue": "result" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -34105,10 +34105,10 @@ ] }, { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -34229,10 +34229,10 @@ ] }, { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -34353,10 +34353,10 @@ ] }, { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -34464,10 +34464,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -35954,10 +35954,10 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36013,10 +36013,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36103,10 +36103,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36416,10 +36416,10 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36475,10 +36475,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36565,10 +36565,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36869,10 +36869,10 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -36928,10 +36928,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -37018,10 +37018,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -37316,10 +37316,10 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -37375,10 +37375,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -37465,10 +37465,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": false }, { @@ -37757,7 +37757,7 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "attrDomainJid", "name": "from", "wireName": "from", "type": "jid", @@ -37816,7 +37816,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrDomainJid", "name": "from", "wireName": "from", "type": "jid", @@ -37897,7 +37897,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrDomainJid", "name": "from", "wireName": "from", "type": "jid", @@ -71783,7 +71783,7 @@ "assertions": [], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -72450,10 +72450,10 @@ "name": "CreateParticipantAddedResponse", "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -73006,7 +73006,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -73673,10 +73673,10 @@ "name": "CreateParticipantAddedResponse", "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -74229,7 +74229,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -79167,10 +79167,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -79457,10 +79457,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -79673,10 +79673,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -79963,10 +79963,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -82645,10 +82645,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -82882,10 +82882,10 @@ "tag": "participant", "children": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "jid", "wireName": "jid", - "type": "jid", + "type": "jid_typed", "required": true }, { @@ -90205,6 +90205,18 @@ "type": "string", "required": true }, + { + "method": "attrFutureTime", + "name": "auth_ttl", + "type": "integer", + "required": true + }, + { + "method": "attrFutureTime", + "name": "ttl", + "type": "integer", + "required": true + }, { "method": "attrInt", "name": "max_buckets", @@ -90317,10 +90329,10 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": true }, { diff --git a/generated/manifest.json b/generated/manifest.json index 9b0c9f2..c839ff8 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -61,7 +61,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "722709bdb6994e533b9eb8db37a35d8ee4dbe5d993b22bb64de945beaab08847" + "sha256": "6df2e802fb23f0993ad7b73d433a5776f2e4faf1c943b2767dc131b5ad0db56f" }, "mex": { "file": "mex/index.json", @@ -71,7 +71,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "e8c5eba1dff86c5b5ae1647a1469861c3cb6fde6e68a8bbe07ba5837e45c019f" + "sha256": "6504509e29cb7b50790f7cf12434b9a75f22843b4240a4cb5423e0e645a9f926" }, "proto": { "file": "proto/WAProto.proto", @@ -80,7 +80,7 @@ "srvreq": { "file": "srvreq/index.json", "schema": "schema/srvreq.schema.json", - "sha256": "d47987b5c0fb452136fee81389457a7bed5089953a079250e1d4152cfeb299c4" + "sha256": "35bda50a8a1924274ff1e5be0a21ef189fa605378150b44b64ad7b41426ed48e" }, "stanza": { "file": "stanza/index.json", diff --git a/generated/notif/index.json b/generated/notif/index.json index a3c2ae5..d158027 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -645,7 +645,7 @@ "literalValue": "crsc_continuation" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1067,7 +1067,7 @@ "literalValue": "passkey_prologue_request" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1601,6 +1601,12 @@ "type": "user_jid", "required": true }, + { + "method": "maybeAttrPhoneUserJid", + "name": "participant_pn", + "type": "user_jid", + "required": false + }, { "method": "attrTime", "name": "t", @@ -2901,7 +2907,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", diff --git a/generated/srvreq/index.json b/generated/srvreq/index.json index d8fa41c..f3694cc 100644 --- a/generated/srvreq/index.json +++ b/generated/srvreq/index.json @@ -20,7 +20,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -479,7 +479,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -553,7 +553,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -666,7 +666,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -819,7 +819,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1011,7 +1011,7 @@ "literalValue": "w:gp2" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1089,7 +1089,7 @@ "literalValue": "passkey_prologue_request" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1165,7 +1165,7 @@ "literalValue": "link_code_companion_reg" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1264,7 +1264,7 @@ "literalValue": "link_code_companion_reg" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -1367,7 +1367,7 @@ "literalValue": "crsc_continuation" }, { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -4971,7 +4971,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -5090,7 +5090,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -5259,7 +5259,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -5496,7 +5496,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -5560,7 +5560,7 @@ ], "fields": [ { - "method": "attrJidWithType", + "method": "literalJid", "name": "from", "wireName": "from", "type": "jid", @@ -5828,10 +5828,10 @@ "name": "FromUser", "fields": [ { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "from", "wireName": "from", - "type": "jid", + "type": "jid_typed", "required": true } ], @@ -5853,10 +5853,10 @@ "required": true }, { - "method": "attrJidWithType", + "method": "attrJidEnum", "name": "participant", "wireName": "participant", - "type": "jid", + "type": "jid_typed", "required": true }, { From 07ddaa2d4216d86a5e56d116c3586f67ef79e916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 21:44:39 -0300 Subject: [PATCH 16/37] review: end the fall-through chain at a break, and honour last-write within a shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four findings on 768a453, all latent; `generated/` is byte-identical. Two are holes in fixes from the two previous rounds, in the layer I did not touch: - Widening the fall-through search from "first non-empty consequent" to "first that yields a shape" removed the only thing that stopped it, so `case A: log(); break; case B: return {…}` published B's action under A's wire tag. The chain now ends at a terminating statement. - The conflict tombstone was made to span the whole merge for `collect_accessor_fields` and left per-call in `merge_action`, so three returns sharing an action type still let `C(jid)` resurrect a key that `A(jid)`/`B(lid)` had killed. The caller owns the tombstone now, at all three grouping sites. And two are semantics I had backwards: - Within ONE shape, a later write wins — a duplicate object key and a later `babelHelpers.extends(…)` operand both override at runtime, so `extends({id: attrString("jid")}, {id: attrString("lid")})` yields `lid` while the IR published `jid`. (Between mutually exclusive BRANCHES the rule is the opposite: they merge, and a genuine disagreement is refused. The two were being conflated.) - A nested `switch` inside an arm was skipped on the theory that it is a different dispatch level. It is not: `case LINK: switch (linkType) { … }` is how that arm picks its shape, and skipping it left the arm empty. The walk descends into switch cases now; nested *functions* are still excluded, which is what keeps a helper's body from swallowing the top-level child-tag dispatch. --- crates/wa-notif/src/actions.rs | 145 +++++++++++++++++++++++---------- crates/wa-notif/src/tests.rs | 47 ++++++++++- 2 files changed, 150 insertions(+), 42 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 5d27c49..a4ccee8 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -421,11 +421,10 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // fall through, and treating that as its own arm loses the action type and every // field of a legally dispatched tag. Search forward for the first case that // yields a result shape instead. - let consequent = switch.cases[i..] - .iter() - .map(|c| &c.consequent[..]) - .find(|c| !arm_result_shapes(c).is_empty()) - .unwrap_or(&case.consequent); + // …but the chain ENDS at a terminating statement. `case A: log(); break; case B: + // return {…}` does not fall through, and scanning past the `break` would publish + // B's action type and fields under A's wire tag — a shape that tag never produces. + let consequent = fall_through_body(&switch.cases[i..]).unwrap_or(&case.consequent); let shapes = arm_result_shapes(consequent); if shapes.is_empty() { // A recognised tag whose arm returns no shape (a bare flag set, an early @@ -441,23 +440,58 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // So branches are grouped by `actionType`: different ones stay separate arms of // the union, identical ones merge into one (a field only some branches read is // optional in the merge). - let mut grouped: Vec = Vec::new(); + // One conflict tombstone per grouped arm, living for every branch folded into it. + let mut grouped: Vec<(NotifActionDef, Conflicts)> = Vec::new(); for (shape, scope) in shapes { for action in expand_shape(&wire_tag, shape, &scope, ctx, 0) { match grouped .iter_mut() - .find(|g| g.action_type == action.action_type) + .find(|(g, _)| g.action_type == action.action_type) { - Some(existing) => merge_action(existing, action), - None => grouped.push(action), + Some((existing, dead)) => merge_action(existing, action, dead), + None => grouped.push((action, Conflicts::new())), } } } - out.extend(grouped); + out.extend(grouped.into_iter().map(|(g, _)| g)); } out } +/// The body a case actually runs, following the fall-through chain. +/// +/// Starting at the case itself, walk forward until one yields a result shape — a label +/// may do setup or log before falling through, so "the first non-empty consequent" is not +/// the answer. The chain **ends** at a terminating statement: `case A: log(); break;` does +/// not fall through, and continuing past it would publish the next case's action under +/// A's wire tag, a shape that tag never produces. +fn fall_through_body<'b, 'a>( + cases: &'b [oxc_ast::ast::SwitchCase<'a>], +) -> Option<&'b [Statement<'a>]> { + for case in cases { + if !arm_result_shapes(&case.consequent).is_empty() { + return Some(&case.consequent); + } + if terminates(&case.consequent) { + return None; + } + } + None +} + +/// Whether a case body ends its own control flow rather than falling through. +fn terminates(stmts: &[Statement]) -> bool { + stmts.iter().any(|s| { + matches!( + s, + Statement::BreakStatement(_) + | Statement::ReturnStatement(_) + | Statement::ContinueStatement(_) + | Statement::ThrowStatement(_) + ) || matches!(s, Statement::BlockStatement(b) if terminates(&b.body)) + }) +} + fn empty_action(wire_tag: String) -> NotifActionDef { NotifActionDef { wire_tag, @@ -471,7 +505,7 @@ fn empty_action(wire_tag: String) -> NotifActionDef { /// Fold a second branch of the same action into the first: a field either branch reads /// is present, and required only if BOTH read it unconditionally. A constant only one /// branch stamps is dropped — it is not a property of the action, only of that branch. -fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { +fn merge_action(into: &mut NotifActionDef, from: NotifActionDef, dead: &mut Conflicts) { // A field the incoming branch does not read at all is, by definition, absent from // one legal shape of this action — so it cannot stay required just because the // first branch read it unconditionally. Weaken those before folding the rest in. @@ -483,9 +517,12 @@ fn merge_action(into: &mut NotifActionDef, from: NotifActionDef) { // Scalars go through the same rule the mapped children use, tombstone included: a // key bound to different wire reads in two branches has no single answer, and // updating only `required` here would publish the first branch's attribute for both. - let mut dead = Conflicts::new(); - merge_fields(&mut into.fields, from.fields, false, &mut dead); - apply_conflicts(&mut into.fields, &dead); + // + // The tombstone is the CALLER's, so it survives the whole fold: with three returns + // sharing an action type, A(jid) vs B(lid) removes the key and a fresh set would let + // C(jid) add it straight back. + merge_fields(&mut into.fields, from.fields, false, dead); + apply_conflicts(&mut into.fields, dead); let mut dead_children: Vec = Vec::new(); for c in from.children { match into.children.iter_mut().find(|x| x.name == c.name) { @@ -531,7 +568,7 @@ fn arm_result_shapes_in<'b, 'a>( } /// Every `return` reachable in `stmts`, descending through control flow (`if`/`else`, -/// blocks, `try`) but never into a nested function or a nested `switch` — an arm or helper that +/// blocks, `try`, a nested `switch`) but never into a nested function — an arm or helper that /// writes `if (cond) return {actionType: A, …}; return {actionType: B, …}` exposes both /// shapes, where a direct-children-only scan would silently keep just the last. /// The result shapes of a function expression, handling the implicit return of an @@ -591,9 +628,17 @@ fn collect_returns<'b, 'a>( } } Statement::TryStatement(t) => collect_returns(&t.block.body, &scope, out), - // Deliberately NOT descending into a nested `switch`: that is a different - // dispatch level, and its arms are other actions' shapes, not branches of - // this one. + // A nested `switch` inside an arm (or a helper body) is how THAT arm picks + // its shape — `case LINK: switch (linkType) { case "parent": return {…}; + // default: return {…} }` describes two legal actions for `link`, and skipping + // it left the arm empty. Nested *functions* are still not descended into, + // which is what keeps the top-level child-tag dispatch out of a helper's + // returns. + Statement::SwitchStatement(sw) => { + for c in &sw.cases { + collect_returns(&c.consequent, &scope, out); + } + } _ => {} } } @@ -750,16 +795,19 @@ fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec = Vec::new(); + let mut merged: Vec<(NotifActionDef, Conflicts)> = Vec::new(); for (shape, scope) in fn_result_shapes(func, &Scope::new()) { for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { - match out.iter_mut().find(|g| g.action_type == action.action_type) { - Some(existing) => merge_action(existing, action), - None => out.push(action), + match merged + .iter_mut() + .find(|(g, _)| g.action_type == action.action_type) + { + Some((existing, dead)) => merge_action(existing, action, dead), + None => merged.push((action, Conflicts::new())), } } } - out + merged.into_iter().map(|(g, _)| g).collect() } /// Read one arm result shape into a [`NotifActionDef`]. @@ -851,18 +899,19 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) // describes two legal shapes, and combining them would make the enclosing action // require both and reject either. `merge_action` weakens what only some branches // carry — the same rule the switch arms use. - let mut branches: Vec = Vec::new(); + let mut branches: Vec<(NotifActionDef, Conflicts)> = Vec::new(); for (shape, scope) in fn_result_shapes(func, &Scope::new()) { let mut one = empty_action(String::new()); fold_shape(shape, &scope, &mut one, ctx, depth + 1); match branches .iter_mut() - .find(|b| b.action_type == one.action_type) + .find(|(b, _)| b.action_type == one.action_type) { - Some(existing) => merge_action(existing, one), - None => branches.push(one), + Some((existing, dead)) => merge_action(existing, one, dead), + None => branches.push((one, Conflicts::new())), } } + let branches: Vec = branches.into_iter().map(|(b, _)| b).collect(); // Distinct action types cannot arise here (a value-position helper contributes // fields, it does not pick the action), so folding the merged branches in is safe. for b in branches { @@ -876,19 +925,25 @@ fn merge_into(def: &mut NotifActionDef, from: NotifActionDef) { if def.action_type.is_none() { def.action_type = from.action_type; } + // A helper folded in as an `extends` operand contributes at ITS position in the + // argument list, so what it writes overrides what an earlier operand wrote — the same + // last-write rule an object literal follows. for f in from.fields { - if !def.fields.iter().any(|x| x.name == f.name) { - def.fields.push(f); + match def.fields.iter_mut().find(|x| x.name == f.name) { + Some(existing) => *existing = f, + None => def.fields.push(f), } } for c in from.children { - if !def.children.iter().any(|x| x.name == c.name) { - def.children.push(c); + match def.children.iter_mut().find(|x| x.name == c.name) { + Some(existing) => *existing = c, + None => def.children.push(c), } } for c in from.constant_fields { - if !def.constant_fields.iter().any(|x| x.name == c.name) { - def.constant_fields.push(c); + match def.constant_fields.iter_mut().find(|x| x.name == c.name) { + Some(existing) => *existing = c, + None => def.constant_fields.push(c), } } } @@ -948,21 +1003,29 @@ fn fold_object<'b, 'a>( // one that returns a flat object contributes its fields under their own names. for mut c in nested.children { c.name = key.to_string(); - if !def.children.iter().any(|x| x.name == c.name) { - def.children.push(c); + match def.children.iter_mut().find(|x| x.name == c.name) { + Some(existing) => *existing = c, + None => def.children.push(c), } } for f in nested.fields { - if !def.fields.iter().any(|x| x.name == f.name) { - def.fields.push(f); + match def.fields.iter_mut().find(|x| x.name == f.name) { + Some(existing) => *existing = f, + None => def.fields.push(f), } } continue; } - if let Some(field) = read_field(key, value, scope, ctx.consts) - && !def.fields.iter().any(|x| x.name == field.name) - { - def.fields.push(field); + if let Some(field) = read_field(key, value, scope, ctx.consts) { + // Last write wins WITHIN one shape: a duplicate key in an object literal, and + // a later `babelHelpers.extends(…)` operand, both override at runtime — + // `extends({id: attrString("jid")}, {id: attrString("lid")})` yields `lid`. + // (Between mutually exclusive BRANCHES the rule is the opposite: they merge, + // and a genuine disagreement is refused. See `merge_fields`.) + match def.fields.iter_mut().find(|x| x.name == field.name) { + Some(existing) => *existing = field, + None => def.fields.push(field), + } } } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 21d8582..e9d80d0 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -598,7 +598,7 @@ __d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o, __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ var cache={}; cache.GROUP_NOTIFICATION_TAG={ADD:"WRONG_add",SUBJECT:"WRONG_subject"}; - var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",GROWTH_LOCKED:"growth_locked",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",GROWTH_LOCKED:"growth_locked",INVITE:"invite",LINK:"link",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ @@ -644,6 +644,15 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr var z = t.attrString("second"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, pick:z}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.INVITE: + o("WALogger").INFO("no fall-through"); break; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LINK: { + var lt = t.attrString("link_type"); + switch (lt) { + case "parent": return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_ADD, parentId:t.attrString("pid")}; + default: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.DESC_REMOVE, subId:t.attrString("sid")}; + } + } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.GROWTH_LOCKED: o("WALogger").INFO("setup"); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { @@ -1115,3 +1124,39 @@ fn a_third_branch_cannot_resurrect_a_conflicted_key() { ); assert!(names.contains(&"id"), "unambiguous keys survive: {names:?}"); } + +#[test] +fn a_terminating_case_does_not_borrow_the_next_action() { + // `case INVITE: log(); break;` does not fall through. Scanning past the `break` for + // "the next case that yields a shape" would publish that action under `invite`, a + // shape the tag never produces. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let invite: Vec<_> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "invite") + .collect(); + assert_eq!(invite.len(), 1); + assert_eq!( + invite[0].action_type, None, + "a terminating arm produces no shape of its own, and must borrow none" + ); + assert!(invite[0].fields.is_empty() && invite[0].children.is_empty()); +} + +#[test] +fn an_arm_selecting_through_a_nested_switch_yields_both_actions() { + // `case UNLINK: switch (linkType) { case "parent": return {…}; default: return {…} }` + // describes two legal actions for one wire tag. Skipping nested switches — which the + // walk used to do, on the theory that they are a different dispatch level — left the + // arm empty. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let mut got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "link") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + got.sort_unstable(); + assert_eq!(got, ["desc_add", "desc_remove"]); +} From 2ab37e28b155cb9a4de0b2e38460cbc354744bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 21:48:49 -0300 Subject: [PATCH 17/37] notif: one implementation of the branch-merge rule; one accessor classifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answering the pattern rather than the instances. Two kinds of knowledge in this branch were written down more than once, and each review round fixed the copy that had been pointed at while the others stayed stale. **One classifier.** `response_smax::normalize_accessor` carried its own table of 23 accessors WITH their types, parallel to `wap::method_field_type`. They had already drifted twice — `attrJidWithType` was `jid` on one side and `jid_typed` on the other for the same wire contract, and the enum accessors were `enum` here and `string` there. The table now decides only the canonical NAME (the genuine aliases: `attrStanzaId` is an `attrString` read, `attrPhoneUserJid` the explicit-PN spelling of `attrUserJid`, `contentBytesRange` a `contentBytes` with a length rule) and takes the type from the classifier. There is no longer a second source of truth to go stale. Output is unchanged, which is the point: delegation reproduces every previous answer. That completes a sweep the same shape as the three enumerate-vs-derive bugs this review already found (`maybeAttr*` types, `is_content_method`, `is_attr_method`) — those are all derived now, and this was the last hand-written table shadowing them. **One branch merge.** The alternative rule — a field either branch reads is present, required only if every branch reads it unconditionally, a key two branches bind to different wire reads is refused — was implemented at three levels: scalars, children, and child fields. Rounds 10 through 13 each fixed one level and left the others, and the child-field level was still creating a fresh conflict set per call when this started, so `A(p)` vs `B(q)` removed a key and `C(p)` put it back. `BranchFold` now owns the rule and every tombstone, for the whole fold rather than one call of it, and the three grouping sites route through it. Composition *within* one shape (`babelHelpers.extends`, a duplicate object key, where the last write simply wins) stays separate and is named as such — conflating the two was the other half of these bugs. A test covers the level that was still broken: a field two branches of a mapped-child callback disagree on stays refused when a third branch agrees with the first. `generated/` is byte-identical. --- crates/wa-notif/src/actions.rs | 142 +++++++++++++++++----------- crates/wa-notif/src/tests.rs | 34 ++++++- crates/wa-scan/src/response_smax.rs | 77 +++++++-------- 3 files changed, 160 insertions(+), 93 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index a4ccee8..98b576e 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -440,20 +440,19 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // So branches are grouped by `actionType`: different ones stay separate arms of // the union, identical ones merge into one (a field only some branches read is // optional in the merge). - // One conflict tombstone per grouped arm, living for every branch folded into it. - let mut grouped: Vec<(NotifActionDef, Conflicts)> = Vec::new(); + let mut grouped: Vec = Vec::new(); for (shape, scope) in shapes { for action in expand_shape(&wire_tag, shape, &scope, ctx, 0) { match grouped .iter_mut() - .find(|(g, _)| g.action_type == action.action_type) + .find(|g| g.def.action_type == action.action_type) { - Some((existing, dead)) => merge_action(existing, action, dead), - None => grouped.push((action, Conflicts::new())), + Some(fold) => fold.absorb(action), + None => grouped.push(BranchFold::new(action)), } } } - out.extend(grouped.into_iter().map(|(g, _)| g)); + out.extend(grouped.into_iter().map(BranchFold::finish)); } out } @@ -502,49 +501,84 @@ fn empty_action(wire_tag: String) -> NotifActionDef { } } -/// Fold a second branch of the same action into the first: a field either branch reads -/// is present, and required only if BOTH read it unconditionally. A constant only one -/// branch stamps is dropped — it is not a property of the action, only of that branch. -fn merge_action(into: &mut NotifActionDef, from: NotifActionDef, dead: &mut Conflicts) { - // A field the incoming branch does not read at all is, by definition, absent from - // one legal shape of this action — so it cannot stay required just because the - // first branch read it unconditionally. Weaken those before folding the rest in. - for existing in into.fields.iter_mut() { - if !from.fields.iter().any(|f| f.name == existing.name) { - existing.required = false; +/// The union of one wire tag's branches, accumulated. +/// +/// **One** implementation of the alternative rule, because writing it per level is what +/// kept going wrong: the rule lived in three places (scalars, children, child fields) and +/// each review round fixed the copy that had been pointed at while the others stayed +/// stale. Every tombstone below lives for the whole fold rather than one call of it — +/// that was the other half of the same bug, since a conflict removed by branch B is +/// re-added by branch C if the state resets in between. +/// +/// The rule itself: a field either branch reads is present; it is required only when +/// EVERY branch reads it unconditionally; and a key two branches bind to *different* wire +/// reads is refused, because reporting one of them describes the other branch's payload +/// wrongly. Distinct from composition *within* one shape (`babelHelpers.extends`, a +/// duplicate object key), where the last write simply wins — see [`merge_into`]. +struct BranchFold { + def: NotifActionDef, + /// Scalar keys two branches disagreed on. + dead: Conflicts, + /// Child names bound to two different wire tags. + dead_children: Conflicts, + /// Per child name, the field keys its branches disagreed on. + dead_child_fields: HashMap, + first: bool, +} + +impl BranchFold { + fn new(def: NotifActionDef) -> Self { + Self { + def, + dead: Conflicts::new(), + dead_children: Conflicts::new(), + dead_child_fields: HashMap::new(), + first: true, } } - // Scalars go through the same rule the mapped children use, tombstone included: a - // key bound to different wire reads in two branches has no single answer, and - // updating only `required` here would publish the first branch's attribute for both. - // - // The tombstone is the CALLER's, so it survives the whole fold: with three returns - // sharing an action type, A(jid) vs B(lid) removes the key and a fresh set would let - // C(jid) add it straight back. - merge_fields(&mut into.fields, from.fields, false, dead); - apply_conflicts(&mut into.fields, dead); - let mut dead_children: Vec = Vec::new(); - for c in from.children { - match into.children.iter_mut().find(|x| x.name == c.name) { - // Two branches mapping the same output name: their element shapes are - // alternatives, so the field sets merge under the same requiredness rule. - // Discarding the later one lost fields only it reads, and left fields only - // the first reads marked required though a legal branch omits them. - Some(existing) if existing.wire_tag == c.wire_tag => { - let mut d = Conflicts::new(); - merge_fields(&mut existing.fields, c.fields, false, &mut d); - apply_conflicts(&mut existing.fields, &d); + + /// Fold one more branch of the same action into the union. + fn absorb(&mut self, from: NotifActionDef) { + self.first = false; + if self.def.action_type.is_none() { + self.def.action_type = from.action_type; + } + merge_fields(&mut self.def.fields, from.fields, false, &mut self.dead); + for c in from.children { + match self.def.children.iter_mut().find(|x| x.name == c.name) { + // Same output name and the same element: their field sets are + // alternatives and merge under the rule above. + Some(existing) if existing.wire_tag == c.wire_tag => { + let dead = self.dead_child_fields.entry(c.name.clone()).or_default(); + merge_fields(&mut existing.fields, c.fields, false, dead); + } + // Same name, different element. Leaving the first would tell a consumer + // every legal shape uses it. + Some(existing) => { + self.dead_children.insert(existing.name.clone()); + } + None => self.def.children.push(c), + } + } + // A constant only some branches stamp is not a property of the action. + self.def + .constant_fields + .retain(|c| from.constant_fields.contains(c)); + } + + /// Apply every tombstone. Run once, after the last branch. + fn finish(mut self) -> NotifActionDef { + apply_conflicts(&mut self.def.fields, &self.dead); + self.def + .children + .retain(|c| !self.dead_children.contains(&c.name)); + for c in &mut self.def.children { + if let Some(dead) = self.dead_child_fields.get(&c.name) { + apply_conflicts(&mut c.fields, dead); } - // Same output name, different wire TAG — two different elements under one - // name. Leaving the first in place would tell a consumer every legal shape - // uses it, so both go. - Some(existing) => dead_children.push(existing.name.clone()), - None => into.children.push(c), } + self.def } - into.children.retain(|c| !dead_children.contains(&c.name)); - into.constant_fields - .retain(|c| from.constant_fields.contains(c)); } /// Every distinct result shape an arm can return: one per branch of a `cond ? A : B` @@ -795,19 +829,19 @@ fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec = Vec::new(); + let mut merged: Vec = Vec::new(); for (shape, scope) in fn_result_shapes(func, &Scope::new()) { for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { match merged .iter_mut() - .find(|(g, _)| g.action_type == action.action_type) + .find(|g| g.def.action_type == action.action_type) { - Some((existing, dead)) => merge_action(existing, action, dead), - None => merged.push((action, Conflicts::new())), + Some(fold) => fold.absorb(action), + None => merged.push(BranchFold::new(action)), } } } - merged.into_iter().map(|(g, _)| g).collect() + merged.into_iter().map(BranchFold::finish).collect() } /// Read one arm result shape into a [`NotifActionDef`]. @@ -899,19 +933,19 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) // describes two legal shapes, and combining them would make the enclosing action // require both and reject either. `merge_action` weakens what only some branches // carry — the same rule the switch arms use. - let mut branches: Vec<(NotifActionDef, Conflicts)> = Vec::new(); + let mut branches: Vec = Vec::new(); for (shape, scope) in fn_result_shapes(func, &Scope::new()) { let mut one = empty_action(String::new()); fold_shape(shape, &scope, &mut one, ctx, depth + 1); match branches .iter_mut() - .find(|(b, _)| b.action_type == one.action_type) + .find(|b| b.def.action_type == one.action_type) { - Some((existing, dead)) => merge_action(existing, one, dead), - None => branches.push((one, Conflicts::new())), + Some(fold) => fold.absorb(one), + None => branches.push(BranchFold::new(one)), } } - let branches: Vec = branches.into_iter().map(|(b, _)| b).collect(); + let branches: Vec = branches.into_iter().map(BranchFold::finish).collect(); // Distinct action types cannot arise here (a value-position helper contributes // fields, it does not pick the action), so folding the merged branches in is safe. for b in branches { diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index e9d80d0..7dd71b6 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -598,7 +598,7 @@ __d("WAWebHandleDeviceNotification",["WADeprecatedWapParser"],(function(t,n,r,o, __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ var cache={}; cache.GROUP_NOTIFICATION_TAG={ADD:"WRONG_add",SUBJECT:"WRONG_subject"}; - var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",GROWTH_LOCKED:"growth_locked",INVITE:"invite",LINK:"link",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); + var e=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",NOT_EPHEMERAL:"not_ephemeral",MODIFY:"modify",GROWTH_LOCKED:"growth_locked",INVITE:"invite",LINK:"link",ANNOUNCE:"announcement",LOCKED:"locked",REVOKE_INVITE:"revoke",DESC:"description",UNLINK:"unlink"}); l.GROUP_NOTIFICATION_TAG=e; }), 1); __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ @@ -606,7 +606,7 @@ __d("WAWebGroupApiConst",[],(function(t,n,r,o,a,i,l){ l.GROUP_PARTICIPANT_TYPES=g; }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ - var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",MODIFY:"modify",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); + var d=Object.freeze({ADD:"add",SUBJECT:"subject",EPHEMERAL:"ephemeral",MODIFY:"modify",ANNOUNCE:"announce",RESTRICT:"restrict",REVOKE_INVITE:"revoke_invite",DESC_ADD:"desc_add",DESC_REMOVE:"desc_remove"}); l.GROUP_ACTIONS=d; }), 1); __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ @@ -616,6 +616,11 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr if (x.hasChild("mid")) return {id:x.attrString("id"), who:x.attrString("lid")}; return {id:x.attrString("id"), who:x.attrString("jid")}; }); } + function q2(e){ + if (e.hasChild("a")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ANNOUNCE, rows:e.mapChildrenWithTag("row", function(x){ return {v:x.attrString("p")}; })}; + if (e.hasChild("b")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ANNOUNCE, rows:e.mapChildrenWithTag("row", function(x){ return {v:x.attrString("q")}; })}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.ANNOUNCE, rows:e.mapChildrenWithTag("row", function(x){ return {v:x.attrString("p")}; })}; + } function y(e,t){ return t.mapChildrenWithTag("participant", function(p){ return { id: p.attrUserJid("jid"), displayName: p.maybeAttrString("display_name"), kind: p.maybeAttrEnum("type", o("WAWebGroupApiConst").GROUP_PARTICIPANT_TYPES) }; }); } @@ -644,6 +649,8 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr var z = t.attrString("second"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, pick:z}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.ANNOUNCE: + return q2(t); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.INVITE: o("WALogger").INFO("no fall-through"); break; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LINK: { @@ -1160,3 +1167,26 @@ fn an_arm_selecting_through_a_nested_switch_yields_both_actions() { got.sort_unstable(); assert_eq!(got, ["desc_add", "desc_remove"]); } + +#[test] +fn a_child_field_conflict_survives_a_third_branch() { + // The alternative rule lives in ONE place now, so a conflict inside a MAPPED CHILD + // gets the same tombstone the top-level scalars do. Before, the child merge created a + // fresh conflict set per call — the same defect as the outer level, one layer down — + // so `A(p)` vs `B(q)` removed `v` and `C(p)` put it back. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let rows = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "announcement") + .expect("announcement arm") + .children + .iter() + .find(|c| c.name == "rows") + .expect("rows child"); + let names: Vec<&str> = rows.fields.iter().map(|f| f.name.as_str()).collect(); + assert!( + !names.contains(&"v"), + "a child field two branches disagree on must stay refused: {names:?}" + ); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 706fedb..b0b239a 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -1192,44 +1192,47 @@ fn content_byte_length( } } -/// Map a smax accessor name → (canonical wap method, field type, byte_length). +/// Map a smax accessor name → its canonical wap method and the type that method decodes +/// to. +/// +/// This table decides **only the name**. The type comes from [`wap::method_field_type`], +/// so there is exactly one classifier in the codebase and the two cannot drift — they +/// already had, twice: `attrJidWithType` carried `jid` here and `jid_typed` there for the +/// same wire contract, and the enum accessors were `enum` on one side and `string` on the +/// other. A local table that repeats the type is a second source of truth waiting to go +/// stale, so this one does not have one. +/// +/// Aliasing is what remains: `attrStanzaId` and `attrCallId` are `attrString` reads under +/// another name, `attrPhoneUserJid` is the explicit-PN spelling of `attrUserJid`, and +/// `contentBytesRange` is `contentBytes` with a length rule. Anything not aliased keeps +/// its own name, which is how the JID flavour — protocol-safety-critical, since a LID and +/// a PN user JID are different identities for the same person — survives. fn normalize_accessor(m: &str) -> Option<(String, ParsedFieldType, Option)> { - let s = |c: &str, t: ParsedFieldType| Some((c.to_string(), t, None)); - match m { - "attrString" | "attrStanzaId" | "attrCallId" | "attrStringFromReference" => { - s(wap::ATTR_STRING, ParsedFieldType::String) - } - "attrInt" | "attrIntRange" => s(wap::ATTR_INT, ParsedFieldType::Integer), - "attrStringEnum" => s(wap::ATTR_ENUM, ParsedFieldType::Enum), - "contentString" => s(wap::CONTENT_STRING, ParsedFieldType::String), - "contentInt" => s(wap::CONTENT_INT, ParsedFieldType::Integer), - "contentStringEnum" => s(wap::ATTR_ENUM, ParsedFieldType::Enum), - "contentBytes" => s(wap::CONTENT_BYTES, ParsedFieldType::Bytes), - "contentBytesRange" => s(wap::CONTENT_BYTES, ParsedFieldType::Bytes), - // Keep the JID flavor each accessor validates rather than collapsing every JID - // to one type. The flavor is protocol-safety-critical: a LID user JID and a PN - // user JID are different identities for the same person. The `phone*` variants - // are the explicit-PN spelling of the plain user/device accessors. - "attrUserJid" | "attrPhoneUserJid" => s(wap::ATTR_USER_JID, ParsedFieldType::UserJid), - "attrLidUserJid" => s(wap::ATTR_LID_USER_JID, ParsedFieldType::LidUserJid), - "attrDeviceJid" | "attrPhoneDeviceJid" => { - s(wap::ATTR_DEVICE_JID, ParsedFieldType::DeviceJid) - } - "attrLidDeviceJid" => s(wap::ATTR_LID_DEVICE_JID, ParsedFieldType::LidDeviceJid), - "attrGroupJid" => s(wap::ATTR_GROUP_JID, ParsedFieldType::GroupJid), - "attrNewsletterJid" => s(wap::ATTR_NEWSLETTER_JID, ParsedFieldType::NewsletterJid), - "attrCallJid" => s(wap::ATTR_CALL_JID, ParsedFieldType::CallJid), - "attrBroadcastJid" => s(wap::ATTR_BROADCAST_JID, ParsedFieldType::BroadcastJid), - "attrStatusJid" => s(wap::ATTR_STATUS_JID, ParsedFieldType::StatusJid), - // Every remaining JID accessor keeps its OWN name and takes its type from the - // shared classifier. Collapsing them onto `attrJidWithType`/`Jid` lost which - // accessor validated the value AND made one method carry two types in the same - // artifact — `attrJidWithType` appeared as `jid` 68 times (this path) and - // `jid_typed` 12 times (the legacy path, via the classifier), for the same wire - // contract. One classifier, one answer. - other if wap::method_field_type(other).is_jid() => s(other, wap::method_field_type(other)), - _ => None, - } + let canonical = match m { + "attrStanzaId" | "attrCallId" | "attrStringFromReference" => wap::ATTR_STRING, + "attrIntRange" => wap::ATTR_INT, + "attrStringEnum" | "contentStringEnum" => wap::ATTR_ENUM, + "contentBytesRange" => wap::CONTENT_BYTES, + "attrPhoneUserJid" => wap::ATTR_USER_JID, + "attrPhoneDeviceJid" => wap::ATTR_DEVICE_JID, + // Not aliased: the accessor names itself, and the classifier types it. + other if is_known_accessor(other) => other, + _ => return None, + }; + Some(( + canonical.to_string(), + wap::method_field_type(canonical), + None, + )) +} + +/// Whether the classifier recognises this accessor as a value read at all. +/// +/// A smax parser calls plenty of helpers that are not field accessors (`assertTag`, +/// `optional`, the child descents), so an unknown name must yield no field rather than a +/// defaulted string — the callers rely on `None` to skip. +fn is_known_accessor(m: &str) -> bool { + wap::is_attr_method(m) || wap::is_content_method(m) } /// The optional (`maybe…`) variant of a canonical method, where one exists. From b02e251c02a6da32759cdf73968e4abe62212ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 22:16:34 -0300 Subject: [PATCH 18/37] scan: resolve response enums on the legacy path too; last-write for every key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight findings on 2ab37e2 (two CodeRabbit, six Codex). Five were the same two rules missing from a site my previous sweep did not reach — so the sweep went one level deeper. **Enum resolution now covers all three paths.** Gating on a list of spellings (`attrStringEnum` / `contentStringEnum`) left `attrEnum`, `maybeAttrEnum`, `contentEnum` and `attrEnumOrNullIfUnknown` typed `enum` with no `enumRef`; the gate is the classifier now. That exposed the real gap: 98 of those fields come from the LEGACY scanner, which had no enum resolution at all. It reads one module at a time and cannot see another's exports, so it now records a *pending* reference — the convention the request-attribute enums already use — and `enum_link` fills it in a pass that runs once the bundle index exists. `fieldEnumRefs` 146 -> 150, and notification action content reads (`contentEnum(TABLE)`) can carry one too. The remainder is a genuine limit, now counted instead of silent: WA composes some enums at runtime (`babelHelpers.extends({}, base, {error: "error"})`, as the privacy settings do), which has no literal value set to recover. Those are reported under `dropsByReason` (33 -> 37) rather than leaving the field looking unconstrained. **Last write wins for every key a shape writes**, not just the scalar fields. `actionType` and the constants were still first-write, so `extends({actionType: A}, {actionType: B})` published `A` where JavaScript yields `B`. Same rule, two more sites. Also: - The fall-through chain returned only the body that returns, discarding statements the earlier labels executed — `case A: var id = …; case B: return {id}` lost `id`. It carries the whole run now. - `errorArms` counted the envelope as an arm, so the manifest reported 646 against 645 present and an envelope appearing could offset an accepted shape disappearing. Alternatives only. - Pins at the variant's own node were filed as an `errorEnvelope` when no deeper node existed, the opposite of what that field documents. They belong to the arms. - Dropped `BranchFold::first`, which nothing read. One --allow-shrink, for the errorArms correction. --- crates/wa-notif/src/actions.rs | 115 ++++++++++++++++++--------- crates/wa-notif/src/tests.rs | 21 ++++- crates/wa-scan/src/enum_link.rs | 56 +++++++++++++ crates/wa-scan/src/lib.rs | 17 +++- crates/wa-scan/src/response.rs | 29 +++++++ crates/wa-scan/src/response_index.rs | 6 +- crates/wa-scan/src/response_smax.rs | 6 +- crates/whatspec/src/main.rs | 5 +- generated/incoming/index.json | 14 +++- generated/iq/index.json | 64 ++++++++++++++- generated/manifest.json | 10 +-- 11 files changed, 288 insertions(+), 55 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 98b576e..9271855 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -424,8 +424,9 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec // …but the chain ENDS at a terminating statement. `case A: log(); break; case B: // return {…}` does not fall through, and scanning past the `break` would publish // B's action type and fields under A's wire tag — a shape that tag never produces. - let consequent = fall_through_body(&switch.cases[i..]).unwrap_or(&case.consequent); - let shapes = arm_result_shapes(consequent); + let run: Vec<&Statement> = + fall_through_body(&switch.cases[i..]).unwrap_or_else(|| as_refs(&case.consequent)); + let shapes = arm_result_shapes_of(&run); if shapes.is_empty() { // A recognised tag whose arm returns no shape (a bare flag set, an early // break). Still catalogued: knowing the tag is dispatched at all beats @@ -466,10 +467,15 @@ fn extract_switch(switch: &SwitchStatement, ctx: &ArmCtx) -> Vec /// A's wire tag, a shape that tag never produces. fn fall_through_body<'b, 'a>( cases: &'b [oxc_ast::ast::SwitchCase<'a>], -) -> Option<&'b [Statement<'a>]> { +) -> Option>> { + // Everything the chain executes, in order — not just the case that returns. A label + // may bind before falling through (`case A: var id = child.attrString("id"); case B: + // return {id}`), and handing back only B's body drops `id` from A's scope. + let mut run: Vec<&Statement> = Vec::new(); for case in cases { + run.extend(case.consequent.iter()); if !arm_result_shapes(&case.consequent).is_empty() { - return Some(&case.consequent); + return Some(run); } if terminates(&case.consequent) { return None; @@ -523,7 +529,6 @@ struct BranchFold { dead_children: Conflicts, /// Per child name, the field keys its branches disagreed on. dead_child_fields: HashMap, - first: bool, } impl BranchFold { @@ -533,13 +538,11 @@ impl BranchFold { dead: Conflicts::new(), dead_children: Conflicts::new(), dead_child_fields: HashMap::new(), - first: true, } } /// Fold one more branch of the same action into the union. fn absorb(&mut self, from: NotifActionDef) { - self.first = false; if self.def.action_type.is_none() { self.def.action_type = from.action_type; } @@ -584,16 +587,31 @@ impl BranchFold { /// Every distinct result shape an arm can return: one per branch of a `cond ? A : B` /// (nested ternaries included), each with its `babelHelpers.extends(…)` merged away. fn arm_result_shapes<'b, 'a>(consequent: &'b [Statement<'a>]) -> Vec> { + arm_result_shapes_of(&as_refs(consequent)) +} + +/// As [`arm_result_shapes`], over a statement list assembled from several sources — the +/// fall-through chain runs the statements of every case it passes through, not only the +/// one that returns. +fn arm_result_shapes_of<'b, 'a>(consequent: &[&'b Statement<'a>]) -> Vec> { arm_result_shapes_in(consequent, &Scope::new()) } +fn as_refs<'b, 'a>(stmts: &'b [Statement<'a>]) -> Vec<&'b Statement<'a>> { + stmts.iter().collect() +} + /// As [`arm_result_shapes`], with an enclosing scope the branches layer over. fn arm_result_shapes_in<'b, 'a>( - consequent: &'b [Statement<'a>], + consequent: &[&'b Statement<'a>], outer: &Scope<'b, 'a>, ) -> Vec> { - let stmts = match consequent { - [Statement::BlockStatement(b)] => &b.body[..], + let unwrapped; + let stmts: &[&Statement] = match consequent { + [Statement::BlockStatement(b)] => { + unwrapped = as_refs(&b.body); + &unwrapped + } other => other, }; let mut out = Vec::new(); @@ -617,12 +635,12 @@ fn fn_result_shapes<'b, 'a>(e: &'b Expression<'a>, base: &Scope<'b, 'a>) -> Vec< return branches.into_iter().map(|x| (x, base.clone())).collect(); } function_body_of(e) - .map(|stmts| arm_result_shapes_in(stmts, base)) + .map(|stmts| arm_result_shapes_in(&as_refs(stmts), base)) .unwrap_or_default() } fn collect_returns<'b, 'a>( - stmts: &'b [Statement<'a>], + stmts: &[&'b Statement<'a>], outer: &Scope<'b, 'a>, out: &mut Vec>, ) { @@ -637,7 +655,7 @@ fn collect_returns<'b, 'a>( // one branch's return read the other branch's attribute — a wrong `wireName` rather // than a missing field. let mut scope = outer.clone(); - for s in stmts { + for s in stmts.iter().copied() { match s { Statement::VariableDeclaration(decl) => { for d in &decl.declarations { @@ -654,14 +672,14 @@ fn collect_returns<'b, 'a>( out.extend(branches.into_iter().map(|e| (e, scope.clone()))); } } - Statement::BlockStatement(b) => collect_returns(&b.body, &scope, out), + Statement::BlockStatement(b) => collect_returns(&as_refs(&b.body), &scope, out), Statement::IfStatement(i) => { - collect_returns(std::slice::from_ref(&i.consequent), &scope, out); + collect_returns(&[&i.consequent], &scope, out); if let Some(alt) = &i.alternate { - collect_returns(std::slice::from_ref(alt), &scope, out); + collect_returns(&[alt], &scope, out); } } - Statement::TryStatement(t) => collect_returns(&t.block.body, &scope, out), + Statement::TryStatement(t) => collect_returns(&as_refs(&t.block.body), &scope, out), // A nested `switch` inside an arm (or a helper body) is how THAT arm picks // its shape — `case LINK: switch (linkType) { case "parent": return {…}; // default: return {…} }` describes two legal actions for `link`, and skipping @@ -670,7 +688,7 @@ fn collect_returns<'b, 'a>( // returns. Statement::SwitchStatement(sw) => { for c in &sw.cases { - collect_returns(&c.consequent, &scope, out); + collect_returns(&as_refs(&c.consequent), &scope, out); } } _ => {} @@ -1010,10 +1028,13 @@ fn fold_object<'b, 'a>( // `link` selects between three actions by its own `link_type` attribute) — // recorded as unknown rather than guessed. let value = deref_ident(value, scope); + // Last write wins for EVERY key a shape writes, not just the scalar fields: + // `extends({actionType: A}, {actionType: B})` and `{duration: 0, duration: 1}` + // both yield the later value at runtime. Fixing this for one kind of key and + // leaving `actionType` and the constants on first-write was the same rule + // written in three places again. if key == "actionType" { - if def.action_type.is_none() { - def.action_type = ctx.consts.resolve(value); - } + def.action_type = ctx.consts.resolve(value); continue; } if let Some(c) = const_value(value) { @@ -1065,11 +1086,13 @@ fn fold_object<'b, 'a>( } fn push_constant(def: &mut NotifActionDef, name: &str, value: NotifConstValue) { - if !def.constant_fields.iter().any(|c| c.name == name) { - def.constant_fields.push(NotifActionConstant { - name: name.to_string(), - value, - }); + let c = NotifActionConstant { + name: name.to_string(), + value, + }; + match def.constant_fields.iter_mut().find(|x| x.name == name) { + Some(existing) => *existing = c, + None => def.constant_fields.push(c), } } @@ -1248,22 +1271,11 @@ fn find_accessor_at<'b, 'a>( if let Some(name) = name && is_wire_accessor(method) { - // An enum accessor takes the enum as a further argument - // (`maybeAttrEnum("type", o("Mod").GROUP_PARTICIPANT_TYPES)`) — often hoisted - // into a local first (`attrEnumOrNullIfUnknown("reason", v)`), so the - // candidate is dereferenced through the scope before being recognised. - let enum_arg = call - .arguments - .iter() - .skip(1) - .filter_map(arg_expr) - .map(|a| deref_ident(a, scope)) - .find(|a| as_member(a).is_some()); return Some(Accessor { method: method.to_string(), wire_name: name.to_string(), content: false, - enum_arg, + enum_arg: enum_table_arg(method, call, scope), }); } // A content read — `X.contentString()`, `X.contentUint()`, … — takes no attribute @@ -1285,7 +1297,10 @@ fn find_accessor_at<'b, 'a>( method: method.to_string(), wire_name: tag.to_string(), content: true, - enum_arg: None, + // `node.contentEnum(TABLE)` validates against a table just as the + // attribute path does; hardcoding `None` here typed the field as an enum + // while denying a consumer its legal values. + enum_arg: enum_table_arg(method, call, scope), }); } } @@ -1296,6 +1311,28 @@ fn find_accessor_at<'b, 'a>( .find_map(|a| find_accessor_at(a, scope, depth + 1)) } +/// The enum table an enum-valued accessor validates against. +/// +/// Gated on the shared classifier rather than a list of spellings, and applied to the +/// attribute and content paths alike — `maybeAttrEnum("type", o("Mod").TABLE)`, +/// `attrEnumOrNullIfUnknown("reason", v)` (hoisted into a local, so the candidate is +/// dereferenced first) and `contentEnum(TABLE)` are the same constraint written three +/// ways. `None` for a non-enum accessor. +fn enum_table_arg<'b, 'a>( + method: &str, + call: &'b oxc_ast::ast::CallExpression<'a>, + scope: &Scope<'b, 'a>, +) -> Option<&'b Expression<'a>> { + if wap::method_field_type(method) != wa_ir::ParsedFieldType::Enum { + return None; + } + call.arguments + .iter() + .filter_map(arg_expr) + .map(|a| deref_ident(a, scope)) + .find(|a| as_member(a).is_some()) +} + /// Whether `method` is a wap accessor that reads a named wire attribute. Keyed on the /// accessor prefix rather than an exhaustive list so a new JID/time flavour is covered /// automatically; `hasAttr` is excluded because it is the *guard*, not the read. diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 7dd71b6..f3e1563 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -662,9 +662,10 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.GROWTH_LOCKED: o("WALogger").INFO("setup"); + var thr = t.attrString("threshold"); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOCKED: { var n; - return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.RESTRICT, value:!0, threshold:(n=t.maybeAttrString("threshold"))!=null?n:void 0, seeded:thr}; } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.UNLINK: return I(t); @@ -1190,3 +1191,21 @@ fn a_child_field_conflict_survives_a_third_branch() { "a child field two branches disagree on must stay refused: {names:?}" ); } + +#[test] +fn a_fall_through_label_contributes_its_own_bindings() { + // `case A: var thr = child.attrString("threshold"); case B: return {…, seeded: thr}` + // — the A path binds before falling through, so handing the shape only B's body + // dropped the field. The chain now carries every statement it executes. + let ir = extract_notif(GROUP_ACTIONS_BUNDLE, "2.3000.test"); + let seeded = notif(&ir, "w:gp2") + .actions + .iter() + .find(|a| a.wire_tag == "growth_locked") + .expect("growth_locked arm") + .fields + .iter() + .find(|f| f.name == "seeded") + .expect("the binding from the fall-through label"); + assert_eq!(seeded.wire_name, "threshold"); +} diff --git a/crates/wa-scan/src/enum_link.rs b/crates/wa-scan/src/enum_link.rs index 86b19c8..69df7e3 100644 --- a/crates/wa-scan/src/enum_link.rs +++ b/crates/wa-scan/src/enum_link.rs @@ -88,6 +88,62 @@ impl<'a> EnumResolver<'a> { } } + /// Fill in (or drop) the pending enum links on a response field tree. + /// + /// Same contract as the request side: a link whose enum resolves to a string-variant + /// set is kept, anything else is dropped rather than half-filled. The legacy scanner + /// records the reference without variants because it reads one module at a time; this + /// runs once the whole bundle index is available. + /// Returns the constraints it had to drop, keyed `module.ENUM@attr`, for + /// `dropsByReason` — an enum WA composes at runtime (`babelHelpers.extends({}, base, + /// {error: "error"})`, as the privacy settings do) has no literal value set to + /// recover, and that is worth reporting rather than leaving the field looking + /// unconstrained. + pub(crate) fn resolve_fields( + &mut self, + fields: &mut [wa_ir::ParsedField], + dropped: &mut std::collections::BTreeSet, + ) { + for f in fields { + if let Some(er) = &f.enum_ref + && er.variants.is_empty() + { + let (module, name) = (er.module.clone(), er.name.clone()); + f.enum_ref = self.lookup(&module, &name); + if f.enum_ref.is_none() { + let attr = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); + dropped.insert(format!("{module}.{name}@{attr}")); + } + } + if let Some(children) = &mut f.children { + self.resolve_fields(children, dropped); + } + for uv in f.union_variants.iter_mut().flatten() { + self.resolve_fields(&mut uv.fields, dropped); + } + } + } + + /// The resolved variants of `module::name`, memoized. `None` when the module is + /// absent, the export is not an enum, or a value is not a string. + fn lookup(&mut self, module: &str, name: &str) -> Option { + let module_slice = &self.module_slice; + let variants = self + .cache + .entry((module.to_string(), name.to_string())) + .or_insert_with(|| { + module_slice.get(module).and_then(|slice| { + wa_enums::resolve_named_enum(slice, module, name).and_then(variants_of) + }) + }) + .clone()?; + Some(AttrEnumRef { + name: name.to_string(), + module: module.to_string(), + variants, + }) + } + /// Resolve a flat list of attributes (a stanza root's own attrs). pub(crate) fn resolve_attrs(&mut self, attrs: &mut [WapAttrDef]) { for a in attrs { diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index 238ecef..c88ffa6 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -264,9 +264,17 @@ pub fn scan_iq_with_diagnostics( // `o("Mod").Enum.VARIANT`, whose definition lives in another module, so it's // filled in cross-module after the scan. (IQ attrs are namespace/type only, so // links here come from the request children.) + let mut enum_drops: std::collections::BTreeSet = Default::default(); let mut enum_resolver = enum_link::EnumResolver::new(module_defs, source); for s in &mut stanzas { enum_resolver.resolve_tree(&mut s.request.children); + // The response side needs the same pass: the legacy parser records an + // `o("Mod").ENUM` reference without variants because it reads one module at a + // time, and only here is the whole bundle index available to fill it. + enum_resolver.resolve_fields(&mut s.response.fields, &mut enum_drops); + for v in &mut s.response.variants { + enum_resolver.resolve_fields(&mut v.fields, &mut enum_drops); + } } // Normalize ordering by an intrinsic key so the output (index.json + the @@ -295,7 +303,14 @@ pub fn scan_iq_with_diagnostics( // during the scan loop above and reports into the same shared collector, so // reading it right after `build_pass` would miss every drop from precisely the // unusual response shapes that fallback exists to handle. - constraint_drops: response_index.drop_counts(), + constraint_drops: { + let mut d = response_index.drop_counts(); + if !enum_drops.is_empty() { + *d.entry("response enum argument not structurally resolvable".to_string()) + .or_default() += enum_drops.len(); + } + d + }, ..stats }, ) diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 346e5c1..90b8b40 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -202,6 +202,34 @@ fn mk_field(method: &str, name: &str, ftype: ParsedFieldType, required: bool) -> /// the field name (else `"content"`), and capturing the `contentBytes(N)` byte length /// when the first argument is a numeric literal — the length WA Web's parser pins the /// wire field to (`child("signature").contentBytes(64)` → 64). +/// The `o("Mod").ENUM_NAME` table an enum-valued accessor validates against, as a +/// **pending** [`wa_ir::AttrEnumRef`] — name and module filled, variants empty. +/// +/// The legacy scanner sees one module at a time and cannot read another's exports, so it +/// records the reference and [`crate::enum_link`] fills it in a post-pass, the same +/// convention the request-attribute enums already use. Without this the field shipped as +/// `"type": "enum"` with nothing saying which values are legal — 98 of them. +fn pending_enum_ref(method: &str, call: &CallExpression) -> Option { + if wap::method_field_type(method) != ParsedFieldType::Enum { + return None; + } + let (obj, name) = call + .arguments + .iter() + .skip(1) + .filter_map(arg_expr) + .find_map(wa_oxc::as_member)?; + let module = wa_oxc::as_call(obj) + .and_then(|c| c.arguments.first()) + .and_then(arg_expr) + .and_then(as_string_lit)?; + Some(wa_ir::AttrEnumRef { + name: name.to_string(), + module: module.to_string(), + variants: Vec::new(), + }) +} + fn field_from_call(method: &str, call: &CallExpression) -> ParsedField { let arg0 = call.arguments.first().and_then(arg_expr); let field_name = arg0.and_then(as_string_lit).unwrap_or("content"); @@ -216,6 +244,7 @@ fn field_from_call(method: &str, call: &CallExpression) -> ParsedField { { f.byte_length = Some(n.value as u32); } + f.enum_ref = pending_enum_ref(method, call); f } diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 1149099..6eb8ca6 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -283,10 +283,14 @@ fn error_vocabulary(fields: &[ParsedField]) -> ErrorVocabulary { let mut by_path: std::collections::BTreeMap, ErrorArm> = Default::default(); pins_by_path(fields, &[], &mut by_path); if from_union && !by_path.is_empty() { + // The deepest pins sit on the node the arms discriminate; anything shallower + // encloses it. When every pin is at the variant's own node there IS no enclosing + // node — they are all shared constraints of the arms, and calling them an + // envelope would say the opposite of what the field documents. let deepest = by_path.keys().map(Vec::len).max().unwrap_or(0); let (inner, outer): (Vec<_>, Vec<_>) = by_path .into_iter() - .partition(|(path, _)| path.len() == deepest && deepest > 0); + .partition(|(path, _)| path.len() == deepest); // Shared constraints of the discriminating node: every arm is subject to them. for (_, shared) in &inner { for arm in &mut v.arms { diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index b0b239a..bda7204 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -979,7 +979,11 @@ fn enum_arg_ref( resolver: &Resolver, site: &str, ) -> Option { - if !matches!(accessor, Some("attrStringEnum") | Some("contentStringEnum")) { + // Gated on the CLASSIFIER, not a local list of spellings. Whitelisting + // `attrStringEnum`/`contentStringEnum` left `attrEnum`, `maybeAttrEnum`, `contentEnum` + // and `attrEnumOrNullIfUnknown` typed `enum` with no `enumRef` and no drop recorded — + // 77 fields whose validation constraint looked absent rather than lost. + if !accessor.is_some_and(|m| wap::method_field_type(m) == ParsedFieldType::Enum) { return None; } // The enum is the only `o("Mod").NAME` member *reference* among the args (the node diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index faa9dc6..e3fd03b 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -888,7 +888,10 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { .chain(v.error_envelope.as_ref()) .filter_map(|a| a.text.clone()), ); - c.error_arms += v.error_arms.len() + usize::from(v.error_envelope.is_some()); + // Alternatives only. Folding the envelope in made the number disagree with + // the artifact (646 reported against 645 present) and let an envelope + // appearing offset an accepted shape disappearing in the floor check. + c.error_arms += v.error_arms.len(); walk_fields(&v.fields, &mut c); } } diff --git a/generated/incoming/index.json b/generated/incoming/index.json index e1e6e48..1c0d517 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -107,7 +107,12 @@ "method": "attrEnum", "name": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "STANZA_MSG_TYPES", + "module": "WAWebHandleMsgCommon", + "variants": [] + } }, { "method": "attrString", @@ -2561,7 +2566,12 @@ "method": "attrEnum", "name": "readreceipts", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ALL_NONE", + "module": "WAWebPrivacySettings", + "variants": [] + } } ] } diff --git a/generated/iq/index.json b/generated/iq/index.json index bb78037..b3a9c1b 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -34880,13 +34880,41 @@ "method": "attrEnum", "name": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ALL_CONTACTS", + "module": "WAWebPrivacySettings", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "contacts", + "value": "contacts" + } + ] + } }, { "method": "attrEnum", "name": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "DEFENSE_MODE_STATE", + "module": "WAWebPrivacySettings", + "variants": [ + { + "name": "off", + "value": "off" + }, + { + "name": "on_standard", + "value": "on_standard" + } + ] + } }, { "method": "attrEnum", @@ -34951,13 +34979,41 @@ "method": "attrEnum", "name": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "ALL_CONTACTS", + "module": "WAWebPrivacySettings", + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "contacts", + "value": "contacts" + } + ] + } }, { "method": "attrEnum", "name": "value", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "DEFENSE_MODE_STATE", + "module": "WAWebPrivacySettings", + "variants": [ + { + "name": "off", + "value": "off" + }, + { + "name": "on_standard", + "value": "on_standard" + } + ] + } }, { "method": "attrEnum", diff --git a/generated/manifest.json b/generated/manifest.json index c839ff8..3142839 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -5,9 +5,9 @@ "iq": { "candidateModules": 207, "constraints": { - "errorArms": 646, + "errorArms": 645, "errorTexts": 38, - "fieldEnumRefs": 146, + "fieldEnumRefs": 150, "fieldLiterals": 1782, "referenceConstraints": 584, "typedErrorVariants": 104 @@ -21,7 +21,7 @@ "dropsByReason": { "iq builder substring present but no AST iq call": 5, "mixin fragment (folded into requests, not a standalone stanza)": 66, - "response enum argument not structurally resolvable": 33 + "response enum argument not structurally resolvable": 37 }, "excludedFragments": 66, "stanzas": 143, @@ -56,12 +56,12 @@ "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "ddb565f1ec990b3b9ac5643acc43fc462524f2eb832459d3c3280969f7c014e0" + "sha256": "0c0f164b1fabd803d44fcc5b8f76e4288a04727066f344608662482b76ec6285" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "6df2e802fb23f0993ad7b73d433a5776f2e4faf1c943b2767dc131b5ad0db56f" + "sha256": "4b862f391074732ad286fe4815e0dde7332f80c8624ca603c73b72c57cecaa12" }, "mex": { "file": "mex/index.json", From 770e80149637a72947736b61ff9ffbda82f64745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Mon, 27 Jul 2026 22:58:42 -0300 Subject: [PATCH 19/37] scan: type every content accessor, and stop the pending-enum sentinel from shipping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 15 — eleven findings, all verified against the bundle before applying. incoming/index.json shipped `ALL_NONE` and `STANZA_MSG_TYPES` with `variants: []`. That is the scanner's internal "resolve me later" marker, and an empty variant list tells a consumer the field admits no legal value at all. Three domains run the legacy parser and only the IQ scan ran the post-pass that fills it in. The marker is now a separate `#[serde(skip)]` field with its own type, so the pending state cannot be expressed in the published IR — a caller that forgets can lose the link, never publish a false one. `ResponseEnumLinker` gives all three domains one entry point, and incoming and notif now resolve: `message.type` gets its seven types, the privacy setting its two. `response.rs` still admitted `contentBytes`/`contentString` and no other content accessor, so `contentUint`, `contentEnum`, `contentBytesRange` and `contentLiteralBytes` produced no typed field on the legacy path — the third time an enumerated list drifted from `method_field_type`. Both halves of the predicate are derived from `wap` now, which recovered the PreKeyBundle ``, `` and `` integer contents. Also recovered: `attrEnumValues("t", ENUM.members())` — the table sits behind a `.members()` call and, for a module-local `$InternalEnum`, in the object's *values* rather than its keys; and `contentLiteralBytes`, which discarded the byte sequence it pins (`typeElementValue` is exactly `05`, not arbitrary bytes). An enum table that is not structurally recoverable is now a counted variant rather than an absence. The six `w:gp2` findings are latent against the pinned bundle, so tests carry them: * a later write evicts the key from all three collections, not just its own — one `write_key` now owns that, and `merge_into` is a fold over it, which also gives a helper operand's `actionType` the last word * a case that returns on only one path still falls through; `terminates` had to learn what "every path" means, since a `switch` whose arms all return does end the case * a reassignment updates the binding at its execution point, and clears it when the new value is not a wire read * a ternary between two real values is not a presence guard: the nullish side decides, either direction, and two genuine sources for one key are refused * a bare-value callback goes through the branch fold like the object-shaped one Each of the five was confirmed to fail without its fix. 522 tests, 12/12 schemas, determinism check clean, reference codegen byte-identical. --- crates/wa-ir/src/iq.rs | 41 +++- crates/wa-notif/src/actions.rs | 267 ++++++++++++++++++++------ crates/wa-notif/src/lib.rs | 8 + crates/wa-notif/src/tests.rs | 150 +++++++++++++++ crates/wa-scan/src/enum_link.rs | 80 ++++++-- crates/wa-scan/src/incoming.rs | 9 +- crates/wa-scan/src/lib.rs | 17 +- crates/wa-scan/src/response.rs | 242 +++++++++++++++++++---- crates/wa-scan/src/response_smax.rs | 74 +++++-- generated/incoming/index.json | 103 +++++++++- generated/iq/index.json | 65 ++++++- generated/manifest.json | 8 +- generated/notif/index.json | 20 +- generated/schema/incoming.schema.json | 2 +- generated/schema/iq.schema.json | 2 +- generated/schema/notif.schema.json | 2 +- generated/schema/srvreq.schema.json | 2 +- generated/schema/stanza.schema.json | 2 +- 18 files changed, 940 insertions(+), 154 deletions(-) diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index 044587b..e3cc195 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -398,6 +398,22 @@ pub struct UnionVariant { pub assertions: Vec, } +/// Scanner-internal state for an enum accessor's allowed-value table, before the +/// cross-module post-pass runs. Never serialized — see [`ParsedField::pending_enum_ref`]. +/// +/// The unresolvable case is a *variant*, not an absence, so it can be counted. An enum +/// whose table the scan could not follow and a field that is simply not an enum must not +/// look alike: the first is a constraint we failed to extract and belongs in +/// `dropsByReason`, the second is nothing at all. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PendingEnum { + /// `o("Mod").ENUM` — a module export the bundle-wide index can finish resolving. + Link(AttrEnumRef), + /// A table that is not a structurally recoverable reference (a computed expression, + /// a runtime-composed set). Carries the wire attribute it constrains, for the report. + Unresolvable, +} + /// One field extracted from a response stanza by a parser. /// /// `method` is the parser accessor used (e.g. `attrString`, `maybeChild`, @@ -456,10 +472,33 @@ pub struct ParsedField { /// [`WapAttrDef::enum_ref`]: WapAttrDef::enum_ref #[serde(default, skip_serializing_if = "Option::is_none")] pub enum_ref: Option, + /// Scanner-internal: an enum reference seen but not yet resolved. + /// + /// The legacy scanner reads one module at a time and cannot follow `o("Mod").ENUM` + /// to its definition, so it records the *reference* here and a cross-module post-pass + /// promotes it into [`enum_ref`] once the whole bundle index exists. + /// + /// It is a separate field, and never serialized, because the pending state must not + /// be expressible in the published IR. It previously lived in [`enum_ref`] with an + /// empty `variants`, and the two callers that skipped the post-pass shipped exactly + /// that: `incoming/index.json` told consumers `ALL_NONE` and `STANZA_MSG_TYPES` admit + /// **no** legal value. Now a caller that forgets can only lose the link, never + /// publish a false one. + /// + /// [`enum_ref`]: ParsedField::enum_ref + #[serde(skip)] + #[cfg_attr(feature = "schema", schemars(skip))] + pub pending_enum_ref: Option, /// The fixed value the parser pins this field to — `literal(attrString, participant, /// "type", "admin")` → `"admin"`, `literal(attrInt, error, "code", 429)` → `"429"`. /// Always the string form of the literal; [`field_type`] says how to read it (a - /// `code` field with `literalValue: "429"` is the integer 429 on the wire). + /// `code` field with `literalValue: "429"` is the integer 429 on the wire). On a + /// [`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding + /// [`WapContent::const_bytes`] already uses on the request side — + /// and [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))` + /// → `literalValue: "05"`, `byteLength: 1`. + /// + /// [`byte_length`]: ParsedField::byte_length /// /// [`required`] tells the two pinning forms apart, and the difference matters to an /// emitter: a **required** literal (`literal`) is a hard discriminator — the diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 9271855..b144bac 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -472,29 +472,57 @@ fn fall_through_body<'b, 'a>( // may bind before falling through (`case A: var id = child.attrString("id"); case B: // return {id}`), and handing back only B's body drops `id` from A's scope. let mut run: Vec<&Statement> = Vec::new(); + let mut produced = false; for case in cases { run.extend(case.consequent.iter()); - if !arm_result_shapes(&case.consequent).is_empty() { - return Some(run); - } + produced |= !arm_result_shapes(&case.consequent).is_empty(); + // Only a case that terminates on EVERY path stops the chain. A shape found here + // is not enough: `case A: if (cond) return X; case B: return Y;` legally produces + // either, and stopping at A published only X. Keep walking and let the collected + // shapes accumulate — `collect_returns` reads them all out of `run`. if terminates(&case.consequent) { - return None; + return produced.then_some(run); } } - None + produced.then_some(run) } -/// Whether a case body ends its own control flow rather than falling through. +/// Whether a case body ends its own control flow on **every** path rather than falling +/// through. +/// +/// "Every path" is what makes it safe for [`fall_through_body`] to stop here, and it is +/// load-bearing now that a collected shape no longer stops the walk: `if (cond) return X;` +/// leaves a path open and must fall through, while `switch (t) { case "a": return X; +/// default: return Y }` closes them all and must not. fn terminates(stmts: &[Statement]) -> bool { - stmts.iter().any(|s| { - matches!( - s, - Statement::BreakStatement(_) - | Statement::ReturnStatement(_) - | Statement::ContinueStatement(_) - | Statement::ThrowStatement(_) - ) || matches!(s, Statement::BlockStatement(b) if terminates(&b.body)) - }) + stmts.iter().any(|s| stmt_exits(s, false)) +} + +/// Whether `s` exits the enclosing switch case on every path. +/// +/// `nested` says whether a bare `break` would leave an inner breakable construct instead +/// of the case — inside a nested `switch`, `break` returns control to the case and is not +/// an exit at all. +fn stmt_exits(s: &Statement, nested: bool) -> bool { + match s { + Statement::ReturnStatement(_) | Statement::ThrowStatement(_) => true, + Statement::BreakStatement(_) | Statement::ContinueStatement(_) => !nested, + Statement::BlockStatement(b) => b.body.iter().any(|s| stmt_exits(s, nested)), + // Both arms, or the statement after the `if` still runs. + Statement::IfStatement(i) => i + .alternate + .as_ref() + .is_some_and(|alt| stmt_exits(&i.consequent, nested) && stmt_exits(alt, nested)), + // Exhaustive only with a `default`; an empty case body falls into the next one, + // so it does not have to exit on its own. + Statement::SwitchStatement(sw) => { + sw.cases.iter().any(|c| c.test.is_none()) + && sw.cases.iter().all(|c| { + c.consequent.is_empty() || c.consequent.iter().any(|s| stmt_exits(s, true)) + }) + } + _ => false, + } } fn empty_action(wire_tag: String) -> NotifActionDef { @@ -665,6 +693,26 @@ fn collect_returns<'b, 'a>( } } } + // `x = child.attrString("lid")` — a reassignment, not a declaration. The + // minifier hoists a `var` to the top and assigns later, so handling only + // declarations left the *initializer* installed at every return below the + // assignment: the runtime read `lid` while the IR published `jid`. + // + // An assignment whose right-hand side is not a wire read REMOVES the binding + // rather than leaving the old one: the name no longer holds what it held, and + // reporting a value that has been overwritten is worse than reporting none. + Statement::ExpressionStatement(e) => { + if let Expression::AssignmentExpression(a) = &e.expression + && let Some(name) = a.left.get_identifier_name() + { + let reads_wire = find_accessor(strip_guard(&a.right), &scope).is_some(); + if reads_wire { + scope.insert(name, &a.right); + } else { + scope.remove(name); + } + } + } Statement::ReturnStatement(r) => { if let Some(arg) = r.argument.as_ref() { let mut branches = Vec::new(); @@ -973,30 +1021,58 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) /// Fold a helper's contribution into the enclosing action, keeping what the enclosing /// object already stated and weakening nothing it owns. +/// One key's value in an action shape, in whichever of the three forms it takes. +/// +/// The three collections are three *representations* of one namespace — a shape's keys — +/// not three independent namespaces. Modelling them separately is what let a key exist +/// twice in different forms. +enum KeyValue { + Field(NotifActionField), + Child(NotifActionChild), + Const(NotifConstValue), +} + +/// Write `key` into `def`, last write wins across **all three** collections. +/// +/// The eviction is the point. Each collection used to replace only within itself, so +/// `extends({id: child.attrString("jid")}, {id: 0})` — which yields just `id: 0` at +/// runtime — published a wire-read field *and* a constant both named `id`, and the +/// reverse order left the stale constant behind. A later write does not merely shadow an +/// earlier one; it replaces it, whatever form the earlier one took. +fn write_key(def: &mut NotifActionDef, key: &str, value: KeyValue) { + def.fields.retain(|x| x.name != key); + def.children.retain(|x| x.name != key); + def.constant_fields.retain(|x| x.name != key); + match value { + KeyValue::Field(f) => def.fields.push(f), + KeyValue::Child(c) => def.children.push(c), + KeyValue::Const(v) => def.constant_fields.push(NotifActionConstant { + name: key.to_string(), + value: v, + }), + } +} + +/// Fold a helper's result into `def` as an `babelHelpers.extends` operand. +/// +/// A helper contributes at ITS position in the argument list, so everything it writes — +/// `actionType` included — overrides what an earlier operand wrote, the same last-write +/// rule an object literal follows. Keeping `actionType` on first-write meant +/// `extends({actionType: A}, helper())` where the helper returns `B` dispatched as `A`. fn merge_into(def: &mut NotifActionDef, from: NotifActionDef) { - if def.action_type.is_none() { + if from.action_type.is_some() { def.action_type = from.action_type; } - // A helper folded in as an `extends` operand contributes at ITS position in the - // argument list, so what it writes overrides what an earlier operand wrote — the same - // last-write rule an object literal follows. for f in from.fields { - match def.fields.iter_mut().find(|x| x.name == f.name) { - Some(existing) => *existing = f, - None => def.fields.push(f), - } + let key = f.name.clone(); + write_key(def, &key, KeyValue::Field(f)); } for c in from.children { - match def.children.iter_mut().find(|x| x.name == c.name) { - Some(existing) => *existing = c, - None => def.children.push(c), - } + let key = c.name.clone(); + write_key(def, &key, KeyValue::Child(c)); } for c in from.constant_fields { - match def.constant_fields.iter_mut().find(|x| x.name == c.name) { - Some(existing) => *existing = c, - None => def.constant_fields.push(c), - } + write_key(def, &c.name.clone(), KeyValue::Const(c.value)); } } @@ -1038,13 +1114,11 @@ fn fold_object<'b, 'a>( continue; } if let Some(c) = const_value(value) { - push_constant(def, key, c); + write_key(def, key, KeyValue::Const(c)); continue; } if let Some(child) = mapped_child(key, value, scope, ctx.consts) { - if !def.children.iter().any(|x| x.name == child.name) { - def.children.push(child); - } + write_key(def, key, KeyValue::Child(child)); continue; } // A helper call in value position (`participants: y(chat, child, tag)`): inline @@ -1058,16 +1132,11 @@ fn fold_object<'b, 'a>( // one that returns a flat object contributes its fields under their own names. for mut c in nested.children { c.name = key.to_string(); - match def.children.iter_mut().find(|x| x.name == c.name) { - Some(existing) => *existing = c, - None => def.children.push(c), - } + write_key(def, key, KeyValue::Child(c)); } for f in nested.fields { - match def.fields.iter_mut().find(|x| x.name == f.name) { - Some(existing) => *existing = f, - None => def.fields.push(f), - } + let fkey = f.name.clone(); + write_key(def, &fkey, KeyValue::Field(f)); } continue; } @@ -1077,25 +1146,11 @@ fn fold_object<'b, 'a>( // `extends({id: attrString("jid")}, {id: attrString("lid")})` yields `lid`. // (Between mutually exclusive BRANCHES the rule is the opposite: they merge, // and a genuine disagreement is refused. See `merge_fields`.) - match def.fields.iter_mut().find(|x| x.name == field.name) { - Some(existing) => *existing = field, - None => def.fields.push(field), - } + write_key(def, key, KeyValue::Field(field)); } } } -fn push_constant(def: &mut NotifActionDef, name: &str, value: NotifConstValue) { - let c = NotifActionConstant { - name: name.to_string(), - value, - }; - match def.constant_fields.iter_mut().find(|x| x.name == name) { - Some(existing) => *existing = c, - None => def.constant_fields.push(c), - } -} - /// A literal an arm stamps unconditionally (`value: !0`, `duration: 0`, `type: "x"`). fn const_value(e: &Expression) -> Option { match e { @@ -1149,6 +1204,14 @@ fn read_field<'b, 'a>( consts: &ConstResolver, ) -> Option { let e = deref_ident(e, scope); + // A ternary between two REAL values is not a presence guard, and treating every + // conditional as one kept the consequent and threw the alternate away: + // `{id: cond ? child.attrString("jid") : child.attrString("lid")}` published an + // optional `jid` and lost `lid` entirely — or lost the field altogether when only + // the alternate read the wire. + if let Some(v) = value_selection(key, e, scope, consts) { + return v; + } let optional_by_guard = is_guarded(e); let acc = find_accessor(strip_guard(e), scope)?; Some(NotifActionField { @@ -1161,6 +1224,78 @@ fn read_field<'b, 'a>( }) } +/// Resolve a `cond ? a : b` whose branches are two real values rather than a value and +/// an absence. +/// +/// Returns `None` when this is not that shape (a plain guard, or not a ternary), so the +/// caller falls through to its normal handling. `Some(None)` means the key is **refused**. +/// +/// The three outcomes follow the rule the branch fold already uses elsewhere: +/// * one side nullish → a presence guard, whichever side reads the wire, optional; +/// * both sides the same wire read → that read; +/// * two different wire reads → refused, because one output key cannot name two wire +/// attributes and picking the consequent is a coin flip the IR would state as fact. +fn value_selection<'b, 'a>( + key: &str, + e: &'b Expression<'a>, + scope: &Scope<'b, 'a>, + consts: &ConstResolver, +) -> Option> { + let Expression::ConditionalExpression(c) = e else { + return None; + }; + // The null-coalesce idiom `(x = maybeAttrString("t")) != null ? x : void 0` hides the + // read in the TEST; `strip_guard` already resolves it, and it is a guard, not a + // selection. Leave it alone. + if as_identifier(strip_guard(&c.consequent)) + .and_then(|n| assigned_in(&c.test, n)) + .is_some() + { + return None; + } + let (a, b) = (strip_guard(&c.consequent), strip_guard(&c.alternate)); + let (ra, rb) = (find_accessor(a, scope), find_accessor(b, scope)); + match (ra, rb) { + // Only one side reads the wire: a presence guard in whichever direction it is + // written. `cond ? null : child.attrString("x")` is as common as the other order + // and used to lose the field completely. + (Some(acc), None) if is_nullish(b) => Some(Some(guarded_field(key, acc, consts))), + (None, Some(acc)) if is_nullish(a) => Some(Some(guarded_field(key, acc, consts))), + (Some(x), Some(y)) => { + if x.wire_name == y.wire_name && x.method == y.method && x.content == y.content { + None // same read on both paths — the plain path handles it + } else { + Some(None) // two sources for one key: refuse + } + } + _ => None, + } +} + +/// Whether the expression is a literal absence — the far side of a presence guard. +fn is_nullish(e: &Expression) -> bool { + match e { + Expression::NullLiteral(_) => true, + Expression::Identifier(i) => i.name == "undefined", + // The minifier writes `undefined` as `void 0`. + Expression::UnaryExpression(u) => u.operator == oxc_ast::ast::UnaryOperator::Void, + _ => false, + } +} + +/// The field an accessor yields when it sits behind a presence guard: always optional, +/// because the guard exists precisely so the attribute may be absent. +fn guarded_field(key: &str, acc: Accessor, consts: &ConstResolver) -> NotifActionField { + NotifActionField { + name: key.to_string(), + wire_name: acc.wire_name, + field_type: wap::method_field_type(&acc.method), + required: false, + content: acc.content, + enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), + } +} + /// Whether the value is read behind a presence guard, so the attribute may be absent. /// /// A ternary and a `guard && value` both gate the read on something. `a || b` / `a ?? b` @@ -1381,19 +1516,27 @@ fn collect_accessor_fields<'b, 'a>( // A callback that returns a bare value rather than an object // (`p => userJidToUserWid(p.attrUserJid("jid"))`) has no property key to name the // field by, so the wire attribute names it — better than reporting no fields at all. + // It goes through `merge_fields` too, one field set per return shape. Appending each + // read directly bypassed the branch fold, so `p => cond ? p.attrString("jid") : + // p.attrString("lid")` reported BOTH as required when each execution reads one. if merged.is_empty() { + let mut bare = 0usize; for (shape, inner) in fn_result_shapes(e, &scope) { - if let Some(acc) = find_accessor(shape, &inner) { - merged.push(NotifActionField { + bare += 1; + let fields = find_accessor(shape, &inner) + .map(|acc| NotifActionField { name: acc.wire_name.clone(), wire_name: acc.wire_name, field_type: wap::method_field_type(&acc.method), required: !wap::is_optional_method(&acc.method), content: acc.content, enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), - }); - } + }) + .into_iter() + .collect(); + merge_fields(&mut merged, fields, bare == 1, &mut dead); } + apply_conflicts(&mut merged, &dead); } for f in merged { if !out.iter().any(|x| x.name == f.name) { diff --git a/crates/wa-notif/src/lib.rs b/crates/wa-notif/src/lib.rs index 1f57969..9931efd 100644 --- a/crates/wa-notif/src/lib.rs +++ b/crates/wa-notif/src/lib.rs @@ -114,6 +114,11 @@ pub fn extract_notif_from_modules( // handler that yields no parser leaves `content` absent (degraded), never // dropping the catalog entry. let slice_by_name = module_slice_index(source, module_defs); + // The legacy parser leaves `o("Mod").ENUM` links pending — it reads one module at a + // time — so this domain runs the same cross-module post-pass the IQ scan does. + // Skipping it did not merely lose the link: the pending marker used to be an + // `enumRef` with no variants, published as "this field admits no value". + let mut enums = wa_scan::ResponseEnumLinker::new(module_defs, source); for n in &mut dispatch.notifications { let Some(module) = n.handler_module.clone() else { continue; @@ -123,6 +128,9 @@ pub fn extract_notif_from_modules( }; let notif_type = n.notif_type.clone(); n.content = notification_content(slice, ¬if_type); + if let Some(content) = &mut n.content { + enums.resolve(&format!("{module}::{notif_type}"), content); + } } // Phase 3: the payload action union. `content` describes the envelope; a handler diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index f3e1563..dde4ea1 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1209,3 +1209,153 @@ fn a_fall_through_label_contributes_its_own_bindings() { .expect("the binding from the fall-through label"); assert_eq!(seeded.wire_name, "threshold"); } + +/// A second `w:gp2` handler covering the shapes the real bundle does not exercise today. +/// +/// Every behaviour below is **latent** against the pinned bundle — the constructs are +/// legal, WA writes some of them elsewhere, and the extractor got them wrong — so a +/// fixture is the only thing that can hold the fix in place. Without it a later +/// refactor would silently restore the old answer and `generated/` would not move. +const GROUP_ACTIONS_EDGE_BUNDLE: &str = r#" +__d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g,r,d,o,e,i,l){ + l.handle = function(){ return (function*(e,t){ + var n = e.attrs; + switch (e.tag) { + case "notification": + switch (n.type) { + case "w:gp2": return yield r("WAWebHandleGroupNotification")(e); + } + } + }); }; +}, 1); +__d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper"}); +}), 1); +__d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ + l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); +}), 1); +__d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ + function hlp(e){ return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, extra:e.attrString("extra")}; } + function h(e){ + var x=e.mapChildrenWithTag("child", function(t){ + switch (t.tag) { + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EVICT: + return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:t.attrString("jid")}, {id:0}); + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.COND: + if (t.hasChild("early")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.PICK: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.REBIND: { + var v=t.attrString("jid"); + v=t.attrString("lid"); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, id:v}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.BARE: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, who:t.mapChildrenWithTag("p", function(p){ return p.hasChild("z") ? p.attrString("jid") : p.attrString("lid"); })}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.HELPER: + return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST}, hlp(t)); + } + }); + return {actions:x}; + } + l.handleGroupNotification=h; +}), 1); +"#; + +/// The single action arm for `tag` in the edge-case bundle. +fn edge_action<'a>(ir: &'a wa_ir::NotifIr, tag: &str) -> &'a wa_ir::NotifActionDef { + let mut found = notif(ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == tag); + let a = found + .next() + .unwrap_or_else(|| panic!("no action arm for wire tag {tag:?}")); + assert!(found.next().is_none(), "expected one arm for {tag:?}"); + a +} + +#[test] +fn a_later_write_evicts_the_key_from_every_collection() { + // `extends({id: attrString("jid")}, {id: 0})` yields only `id: 0` at runtime. + // Replacing within each collection separately published BOTH a wire-read field and a + // constant named `id` — a shape no execution produces. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "evict"); + assert!( + !a.fields.iter().any(|f| f.name == "id"), + "the constant must evict the earlier wire read: {:?}", + a.fields + ); + assert_eq!( + a.constant_fields.iter().filter(|c| c.name == "id").count(), + 1, + "exactly one `id`, and it is the constant" + ); +} + +#[test] +fn a_conditionally_returning_case_still_falls_through() { + // `case COND: if (cond) return FIRST; case PICK: return SECOND;` — tag `cond` can + // legally produce either. Stopping at the first shape published only FIRST. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let cond: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "cond") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert!( + cond.contains(&"second"), + "the fall-through target must be reachable from `cond`: {cond:?}" + ); +} + +#[test] +fn a_reassignment_replaces_the_declared_binding() { + // `var v = attrString("jid"); v = attrString("lid"); return {id: v}` reads `lid`. + // Tracking declarations only left the initializer installed. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "rebind"); + let id = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the `id` field survives the reassignment"); + assert_eq!( + id.wire_name, "lid", + "the value at the return is the reassigned one" + ); +} + +#[test] +fn a_bare_callback_read_is_optional_when_only_one_branch_takes_it() { + // `p => cond ? p.attrString("jid") : p.attrString("lid")` reads exactly one per + // element. The bare-value fallback bypassed the branch fold and marked both required. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "bare"); + let child = a + .children + .iter() + .find(|c| c.name == "who") + .expect("the mapped child is recovered"); + assert!( + child.fields.iter().all(|f| !f.required), + "a read taken on only one branch is not required: {:?}", + child.fields + ); +} + +#[test] +fn a_helper_operand_replaces_the_earlier_action_type() { + // `extends({actionType: FIRST}, hlp(t))` where the helper returns SECOND yields + // SECOND at runtime. Copying the helper's `actionType` only into an empty slot + // dispatched the shape as FIRST. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "helper"); + assert_eq!( + a.action_type.as_deref(), + Some("second"), + "the later operand wins, `actionType` included" + ); +} diff --git a/crates/wa-scan/src/enum_link.rs b/crates/wa-scan/src/enum_link.rs index 69df7e3..bc1759a 100644 --- a/crates/wa-scan/src/enum_link.rs +++ b/crates/wa-scan/src/enum_link.rs @@ -94,32 +94,51 @@ impl<'a> EnumResolver<'a> { /// set is kept, anything else is dropped rather than half-filled. The legacy scanner /// records the reference without variants because it reads one module at a time; this /// runs once the whole bundle index is available. - /// Returns the constraints it had to drop, keyed `module.ENUM@attr`, for + /// Returns the constraints it had to drop, keyed `site\x01module.ENUM@attr`, for /// `dropsByReason` — an enum WA composes at runtime (`babelHelpers.extends({}, base, /// {error: "error"})`, as the privacy settings do) has no literal value set to /// recover, and that is worth reporting rather than leaving the field looking /// unconstrained. + /// + /// `site` identifies the parser the loss belongs to, so two parsers losing the same + /// enum on the same attribute count as two — the smax counter learned this and this + /// pass repeated the site-less key. It must stay stable across the mirrored + /// `response.fields` / `response.variants` traversal of one parser, which is how the + /// same loss seen twice still counts once. pub(crate) fn resolve_fields( &mut self, + site: &str, fields: &mut [wa_ir::ParsedField], dropped: &mut std::collections::BTreeSet, ) { for f in fields { - if let Some(er) = &f.enum_ref - && er.variants.is_empty() - { - let (module, name) = (er.module.clone(), er.name.clone()); - f.enum_ref = self.lookup(&module, &name); - if f.enum_ref.is_none() { - let attr = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); - dropped.insert(format!("{module}.{name}@{attr}")); + // Taken, not read: the pending reference is consumed here whether or not it + // resolves, so a second traversal of the same tree cannot count the same loss + // twice and nothing is left half-promoted. + if let Some(pending) = f.pending_enum_ref.take() { + let attr = f.wire_name.clone().unwrap_or_else(|| f.name.clone()); + // Both halves of the pending state are losses when they don't land: a + // link whose module export won't resolve, and a table that was never a + // resolvable reference at all. Counting only the first left the second + // looking like an enum with no constraint. + let lost = match pending { + wa_ir::PendingEnum::Link(er) => { + f.enum_ref = self.lookup(&er.module, &er.name); + f.enum_ref + .is_none() + .then(|| format!("{}.{}", er.module, er.name)) + } + wa_ir::PendingEnum::Unresolvable => Some("".to_string()), + }; + if let Some(what) = lost { + dropped.insert(format!("{site}\u{1}{what}@{attr}")); } } if let Some(children) = &mut f.children { - self.resolve_fields(children, dropped); + self.resolve_fields(site, children, dropped); } for uv in f.union_variants.iter_mut().flatten() { - self.resolve_fields(&mut uv.fields, dropped); + self.resolve_fields(site, &mut uv.fields, dropped); } } } @@ -172,6 +191,45 @@ fn variants_of(def: wa_ir::InternalEnumDef) -> Option> { (!variants.is_empty()).then_some(variants) } +/// Cross-module resolution of the pending enum links a legacy parser leaves on a +/// response shape — the one public entry point for it. +/// +/// A parser records `o("Mod").ENUM` without variants because it reads a single module; +/// only a caller holding the whole bundle can finish the job. Every domain that runs +/// [`crate::parse_module_wap_parsers`] needs this, and the traversal it needs (the +/// shape's own fields *and* each variant's, under one site key) is written here once +/// instead of at each call site — the IQ scan had it, incoming and notif did not, and +/// their artifacts shipped links that resolved to nothing. +pub struct ResponseEnumLinker<'a> { + inner: EnumResolver<'a>, + /// Losses keyed `site\x01module.ENUM@attr`; see [`EnumResolver::resolve_fields`]. + dropped: std::collections::BTreeSet, +} + +impl<'a> ResponseEnumLinker<'a> { + pub fn new(defs: &'a [ModuleDefinition], source: &'a str) -> Self { + Self { + inner: EnumResolver::new(defs, source), + dropped: Default::default(), + } + } + + /// Resolve every pending link in `shape`, attributing losses to `site`. + pub fn resolve(&mut self, site: &str, shape: &mut wa_ir::iq::ParsedResponse) { + self.inner + .resolve_fields(site, &mut shape.fields, &mut self.dropped); + for v in &mut shape.variants { + self.inner + .resolve_fields(site, &mut v.fields, &mut self.dropped); + } + } + + /// The links seen but not resolvable, for `dropsByReason`. + pub fn into_drops(self) -> std::collections::BTreeSet { + self.dropped + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/wa-scan/src/incoming.rs b/crates/wa-scan/src/incoming.rs index 967d12e..7fd5ad2 100644 --- a/crates/wa-scan/src/incoming.rs +++ b/crates/wa-scan/src/incoming.rs @@ -20,6 +20,7 @@ use std::collections::{HashMap, HashSet}; use wa_ir::{AssertionKind, IncomingDef, IncomingTag}; use wa_transform::ModuleDefinition; +use crate::enum_link::ResponseEnumLinker; use crate::response::parse_module_wap_parsers; use crate::response_smax::{Resolver, analyze_module_exports}; @@ -42,6 +43,11 @@ fn incoming_tag(tag: &str) -> Option { pub fn scan_incoming_from_modules(source: &str, defs: &[ModuleDefinition]) -> Vec { let mut out = Vec::new(); let mut seen = HashSet::new(); + // The legacy parser records `o("Mod").ENUM` as a pending link because it reads one + // module at a time; this domain has the whole bundle, so it runs the same + // cross-module post-pass the IQ scan does. Without it the links were simply lost — + // `message.type`'s `STANZA_MSG_TYPES` and `ALL_NONE` among them. + let mut enums = ResponseEnumLinker::new(defs, source); for m in defs { let slice = &source[m.start..m.end]; // Cheap pre-filter; the AST parse in `parse_module_wap_parsers` confirms the @@ -49,7 +55,8 @@ pub fn scan_incoming_from_modules(source: &str, defs: &[ModuleDefinition]) -> Ve if !slice.contains("WADeprecatedWapParser") || !seen.insert(m.name.as_str()) { continue; } - for shape in parse_module_wap_parsers(slice) { + for mut shape in parse_module_wap_parsers(slice) { + enums.resolve(&format!("{}::{}", m.name, shape.parser_name), &mut shape); // The parser's `assertTag("…")` is the received stanza's tag. // `assertTag("receipt")` stores the tag in the assertion's `name`. let tag = shape diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index c88ffa6..6706bb6 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -23,6 +23,7 @@ mod send_parser_index; mod srvreq; mod stanza; +pub use enum_link::ResponseEnumLinker; pub use incoming::scan_incoming_from_modules; pub use mixin_index::MixinIndex; pub use module::{CrossModuleStat, DropReason, scan_module_outcome, scan_module_source}; @@ -264,18 +265,18 @@ pub fn scan_iq_with_diagnostics( // `o("Mod").Enum.VARIANT`, whose definition lives in another module, so it's // filled in cross-module after the scan. (IQ attrs are namespace/type only, so // links here come from the request children.) - let mut enum_drops: std::collections::BTreeSet = Default::default(); let mut enum_resolver = enum_link::EnumResolver::new(module_defs, source); + let mut enum_linker = ResponseEnumLinker::new(module_defs, source); for s in &mut stanzas { enum_resolver.resolve_tree(&mut s.request.children); - // The response side needs the same pass: the legacy parser records an - // `o("Mod").ENUM` reference without variants because it reads one module at a - // time, and only here is the whole bundle index available to fill it. - enum_resolver.resolve_fields(&mut s.response.fields, &mut enum_drops); - for v in &mut s.response.variants { - enum_resolver.resolve_fields(&mut v.fields, &mut enum_drops); - } + // The response side needs the same pass, and gets it from the shared linker so + // the fields/variants traversal is not re-derived here. The parser, not the enum, + // is the unit of loss: two parsers that both fail to resolve `ENUM_OFF_ON` on + // `state` are two unrecovered constraints. + let site = format!("{}::{}", s.module_name, s.response.parser_name); + enum_linker.resolve(&site, &mut s.response); } + let enum_drops = enum_linker.into_drops(); // Normalize ordering by an intrinsic key so the output (index.json + the // grouped codegen) is independent of bundle/source order, matching every diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 90b8b40..d8a1713 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -160,12 +160,19 @@ pub(crate) struct ParserResult { pub fields: Vec, } -/// Accessors that read a value off a node, as the parser treats them: the shared -/// attribute accessors ([`wap::is_attr_method`]) plus the `contentBytes` / -/// `contentString` leaves. Broader than codegen's notion of an attr field — both +/// Accessors that read a value off a node, as the parser treats them: every shared +/// attribute accessor ([`wap::is_attr_method`]) plus every content leaf +/// ([`wap::is_content_method`]). Broader than codegen's notion of an attr field — both /// draw method names from the shared [`wap`] vocabulary so they can't drift. -fn is_attr_method(m: &str) -> bool { - wap::is_attr_method(m) || m == wap::CONTENT_BYTES || m == wap::CONTENT_STRING +/// +/// Both halves are *derived*. Naming `contentBytes`/`contentString` here and nothing +/// else was the enumerate-vs-derive bug a third time: `method_field_type` learned to +/// decode `contentUint`, `contentEnum`, `contentBytesRange` and `contentLiteralBytes`, +/// but this list did not, so on the legacy path those reads produced no typed field at +/// all — only a coarse `contentType` on the parent, losing the integer, enum, range and +/// literal constraints. +fn is_value_method(m: &str) -> bool { + wap::is_attr_method(m) || wap::is_content_method(m) } fn is_assert_method(m: &str) -> bool { @@ -209,30 +216,86 @@ fn mk_field(method: &str, name: &str, ftype: ParsedFieldType, required: bool) -> /// records the reference and [`crate::enum_link`] fills it in a post-pass, the same /// convention the request-attribute enums already use. Without this the field shipped as /// `"type": "enum"` with nothing saying which values are legal — 98 of them. -fn pending_enum_ref(method: &str, call: &CallExpression) -> Option { +fn pending_enum_ref(method: &str, call: &CallExpression, module_scope: &ModuleScope) -> EnumSource { if wap::method_field_type(method) != ParsedFieldType::Enum { - return None; + return EnumSource::NotAnEnum; } - let (obj, name) = call - .arguments - .iter() - .skip(1) - .filter_map(arg_expr) - .find_map(wa_oxc::as_member)?; - let module = wa_oxc::as_call(obj) - .and_then(|c| c.arguments.first()) - .and_then(arg_expr) - .and_then(as_string_lit)?; - Some(wa_ir::AttrEnumRef { - name: name.to_string(), - module: module.to_string(), - variants: Vec::new(), - }) + // A content accessor takes its table as the FIRST argument (`contentEnum(TABLE)`); + // an attribute one names the attribute first (`attrStringEnum("state", TABLE)`). + // Reading past the name unconditionally found nothing on the content spellings. + let skip = usize::from(!wap::is_content_method(method)); + let Some(arg) = call.arguments.iter().skip(skip).filter_map(arg_expr).next() else { + return EnumSource::Unresolved; + }; + // `attrEnumValues("type", o("Mod").CiphertextType.members())` — the allowed set is + // the enum's `.members()`, so the table is the *callee's object*, not the call. + let stripped = strip_members_call(arg); + let via_members = !std::ptr::eq(stripped, arg); + let arg = stripped; + // FORM A: a cross-module export. Recorded as pending; `enum_link` finishes it. + if let Some((obj, name)) = wa_oxc::as_member(arg) + && let Some(module) = wa_oxc::as_call(obj) + .and_then(|c| c.arguments.first()) + .and_then(arg_expr) + .and_then(as_string_lit) + { + return EnumSource::Pending(wa_ir::AttrEnumRef { + name: name.to_string(), + module: module.to_string(), + variants: Vec::new(), + }); + } + // FORM B: a module-local table (`attrEnumValues("mediatype", u.members())`, where + // `u = n("$InternalEnum")({Image: "image", …})`). The allowed set is already in this + // module, so it becomes `enumKeys` directly — no cross-module post-pass, and no + // `enumRef` named after a minified local. + if let Some(name) = as_identifier(arg) { + let table = if via_members { + module_scope.members.get(name) + } else { + module_scope.maps.get(name) + }; + if let Some(values) = table.filter(|v| !v.is_empty()) { + return EnumSource::Keys(values.clone()); + } + } + EnumSource::Unresolved } -fn field_from_call(method: &str, call: &CallExpression) -> ParsedField { +/// Where an enum accessor's allowed-value set came from. +/// +/// `Unresolved` exists so an enum whose table is not structurally recoverable is +/// *counted* rather than published as an unconstrained `"type": "enum"` — the same +/// "missing beats wrong, but never silent" rule the rest of the scan follows. +enum EnumSource { + NotAnEnum, + Pending(wa_ir::AttrEnumRef), + Keys(Vec), + Unresolved, +} + +/// Unwrap a trailing `.members()` / `.getMembers()` accessor, which WA uses to spell +/// "the allowed values of this enum"; anything else is returned unchanged. +fn strip_members_call<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { + wa_oxc::as_call(e) + .filter(|c| c.arguments.is_empty()) + .and_then(|c| match callee_method(c) { + Some("members" | "getMembers") => callee_object(c), + _ => None, + }) + .unwrap_or(e) +} + +fn field_from_call(method: &str, call: &CallExpression, module_scope: &ModuleScope) -> ParsedField { let arg0 = call.arguments.first().and_then(arg_expr); - let field_name = arg0.and_then(as_string_lit).unwrap_or("content"); + // A content accessor reads the element's content and names no attribute, so its + // first argument is never the field name — `contentEnum(TABLE)` would otherwise be + // named after nothing at all. + let field_name = if wap::is_content_method(method) { + "content" + } else { + arg0.and_then(as_string_lit).unwrap_or("content") + }; let mut f = mk_field( method, field_name, @@ -244,7 +307,12 @@ fn field_from_call(method: &str, call: &CallExpression) -> ParsedField { { f.byte_length = Some(n.value as u32); } - f.enum_ref = pending_enum_ref(method, call); + match pending_enum_ref(method, call, module_scope) { + EnumSource::Pending(er) => f.pending_enum_ref = Some(wa_ir::PendingEnum::Link(er)), + EnumSource::Keys(keys) => f.enum_keys = Some(keys), + EnumSource::Unresolved => f.pending_enum_ref = Some(wa_ir::PendingEnum::Unresolvable), + EnumSource::NotAnEnum => {} + } f } @@ -449,13 +517,13 @@ impl ParserAnalyzer<'_, '_> { } // ── Attr/content accessor on the param directly ── - if is_attr_method(method) && obj_is_param { - self.fields.push(field_from_call(method, call)); + if is_value_method(method) && obj_is_param { + self.fields.push(field_from_call(method, call, self.module)); return; } // ── Attr method chained on a child() result: e.child("error").attrInt("code") ── - if is_attr_method(method) + if is_value_method(method) && let Some((parent_tag, inner_method)) = self.child_call_parent(obj) { let idx = find_or_create_field( @@ -464,7 +532,11 @@ impl ParserAnalyzer<'_, '_> { inner_method, inner_method == "child", ); - push_child_field(&mut self.fields, idx, field_from_call(method, call)); + push_child_field( + &mut self.fields, + idx, + field_from_call(method, call, self.module), + ); return; } @@ -515,13 +587,17 @@ impl ParserAnalyzer<'_, '_> { } // ── Attr methods on a tracked child var: t.attrString("name") ── - if is_attr_method(method) + if is_value_method(method) && let Some(parent_tag) = as_identifier(obj) .and_then(|n| self.child_vars.get(n)) .cloned() { let idx = find_or_create_field(&mut self.fields, &parent_tag, "child", true); - push_child_field(&mut self.fields, idx, field_from_call(method, call)); + push_child_field( + &mut self.fields, + idx, + field_from_call(method, call, self.module), + ); } // ── content methods on a tracked child var ── @@ -651,6 +727,9 @@ struct ModuleScope { /// object-map name → its keys in source order — the allowed value set an enum accessor /// (`attrEnumOrNullIfUnknown("attr", map)`) validates a wire attr against. maps: HashMap>, + /// The same maps by their string *values* — what `.members()` yields for a + /// module-local enum (`attrEnumValues("mediatype", u.members())`). + members: HashMap>, } impl ModuleScope { @@ -667,12 +746,14 @@ impl ModuleScope { module_source, functions: HashMap::new(), maps: HashMap::new(), + members: HashMap::new(), fn_depth: 0, }; b.visit_program(&ret.program); Self { functions: b.functions, maps: b.maps, + members: b.members, } } } @@ -681,6 +762,7 @@ struct ModuleScopeBuilder<'a> { module_source: &'a str, functions: HashMap, String)>, maps: HashMap>, + members: HashMap>, /// Number of function bodies we are currently inside. The bundle wraps every module /// in one factory (`__d("M",…,function(…){ })`), so a module-scope /// sibling helper — the only kind `try_helper_descent` may resolve a bare-identifier @@ -741,14 +823,31 @@ impl<'a> Visit<'a> for ModuleScopeBuilder<'_> { self.record_fn(name.as_str(), &f.params, body.span); } } - // `var name = { key: val, … }` — an enum value map. + // `var name = { key: val, … }` — an enum value map — or the same object + // wrapped in a one-argument factory, which is how WA declares a + // module-local enum: `var u = n("$InternalEnum")({Image: "image", …})`. _ => { - if let Some(obj) = wa_oxc::as_object(init) { - self.maps - .entry(name.as_str().to_string()) - .or_insert_with(|| { - wa_oxc::obj_props(obj).map(|(k, _)| k.to_string()).collect() - }); + let obj = wa_oxc::as_object(init).or_else(|| { + wa_oxc::as_call(init) + .filter(|c| c.arguments.len() == 1) + .and_then(|c| arg_expr(&c.arguments[0])) + .and_then(wa_oxc::as_object) + }); + if let Some(obj) = obj { + let n = name.as_str().to_string(); + // Keys and values are both allowed-value sets, for *different* + // accessors: `attrEnumOrNullIfUnknown("t", map)` validates against + // the keys, while `attrEnumValues("t", enum.members())` validates + // against the members — the wire values. Recording only the keys + // left every `.members()` table unresolvable. + self.maps.entry(n.clone()).or_insert_with(|| { + wa_oxc::obj_props(obj).map(|(k, _)| k.to_string()).collect() + }); + self.members.entry(n).or_insert_with(|| { + wa_oxc::obj_props(obj) + .filter_map(|(_, v)| as_string_lit(v).map(str::to_string)) + .collect() + }); } } } @@ -1331,4 +1430,71 @@ mod tests { Some(["delivery", "read", "played"].map(String::from).as_slice()) ); } + + #[test] + fn a_typed_content_accessor_becomes_a_typed_field() { + // `contentUint` was outside the local attr/content list, so a read of it produced + // no field at all — only a coarse `contentType` on the parent. Both halves of the + // predicate are derived from `wap` now, so every spelling `method_field_type` + // decodes reaches field extraction. + let r = analyze_parser_ast(r#"{ e.child("registration").contentUint(); }"#, "e"); + let parent = r + .fields + .iter() + .find(|f| f.tag.as_deref() == Some("registration")) + .expect("the child is recovered"); + let leaf = parent + .children + .as_ref() + .and_then(|c| c.first()) + .expect("its content read is a field"); + assert_eq!(leaf.method, "contentUint"); + assert_eq!(leaf.field_type, ParsedFieldType::Integer); + assert_eq!(leaf.name, "content", "a content read names no attribute"); + } + + #[test] + fn attr_enum_values_recovers_a_module_local_enums_members() { + // `attrEnumValues("mediatype", u.members())` where `u` is a module-local + // `$InternalEnum`. The table is neither an `o("Mod").ENUM` member (so the + // cross-module post-pass cannot see it) nor a bare map identifier — it is the + // enum's VALUES. Reading past the `.members()` call and taking the values is what + // turns an apparently unconstrained `"type": "enum"` into the legal set. + let module = r#"__d("M",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var u=n("$InternalEnum")({Image:"image",Video:"video"}); + var c=new(r("WADeprecatedWapParser"))("p", function(e){ + e.assertTag("message"); + e.child("media").attrEnumValues("mediatype", u.members()); + return {}; + }); + }),1);"#; + let out = parse_module_wap_parsers(module); + let p = out.iter().find(|r| r.parser_name == "p").expect("parser"); + let media = p + .fields + .iter() + .find(|f| f.tag.as_deref() == Some("media")) + .expect("the child"); + let field = media + .children + .as_ref() + .and_then(|c| c.iter().find(|f| f.name == "mediatype")) + .expect("the mediatype attr"); + assert_eq!( + field.enum_keys.as_deref(), + Some(["image", "video"].map(String::from).as_slice()), + "`.members()` yields the enum's VALUES, not its keys" + ); + } + + #[test] + fn an_unresolvable_enum_table_is_marked_pending_not_silently_dropped() { + // An enum accessor whose table is a computed expression must be distinguishable + // from a field that is not an enum at all — the first is a constraint we failed + // to extract and belongs in `dropsByReason`. + let r = analyze_parser_ast(r#"{ e.attrStringEnum("state", pick()); }"#, "e"); + let f = r.fields.iter().find(|f| f.name == "state").expect("field"); + assert_eq!(f.pending_enum_ref, Some(wa_ir::PendingEnum::Unresolvable)); + assert!(f.enum_keys.is_none() && f.enum_ref.is_none()); + } } diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index bda7204..4cbc78e 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -713,21 +713,36 @@ fn classify_call( } Binding::None } - // `contentLiteralBytes(node, [..])` pins the node's content to a fixed byte - // sequence and returns it — a real bytes field when named in `makeResult`. - "contentLiteralBytes" => Binding::Field { - method: wap::CONTENT_BYTES.to_string(), - field_type: ParsedFieldType::Bytes, - required: true, - byte_length: None, - byte_range: None, - int_range: None, - wire_name: None, - source_path, - literal_value: None, - enum_ref: None, - reference_path: None, - }, + // `contentLiteralBytes(node, new Uint8Array([5]))` pins the node's content to a + // fixed byte sequence and returns it — a real bytes field when named in + // `makeResult`, but one where exactly one value is legal. Dropping the sequence + // published an unconstrained `bytes` field, telling an emitter any payload would + // do. The value rides as hex on `literalValue`, the same encoding the request + // side's `WapContent::const_bytes` uses, with its length on `byteLength`. + "contentLiteralBytes" => { + let bytes = args.get(1).and_then(arg_expr).and_then(static_byte_literal); + if bytes.is_none() { + resolver.drop_note_keyed( + "contentLiteralBytes sequence not statically resolvable", + site.to_string(), + ); + } + Binding::Field { + method: wap::CONTENT_BYTES.to_string(), + field_type: ParsedFieldType::Bytes, + required: true, + byte_length: bytes.as_ref().map(|b| b.len() as u32), + byte_range: None, + int_range: None, + wire_name: None, + source_path, + literal_value: bytes + .as_ref() + .map(|b| b.iter().map(|x| format!("{x:02x}")).collect::()), + enum_ref: None, + reference_path: None, + } + } // `optionalLiteral(ACC, node, "attr", "value")` pins the attr *when present* — // a present-or-absent marker, so it is NOT a variant discriminator (absence // satisfies every sibling) and deliberately records no assertion. The pinned @@ -2017,6 +2032,35 @@ impl Resolver<'_> { } } +/// A compile-time byte sequence: `new Uint8Array([5])`, `Uint8Array.of(1, 2)`, or a bare +/// `[0, 1]`. `None` for anything computed — the caller counts that as a dropped +/// constraint rather than publishing the field as unconstrained. +fn static_byte_literal(e: &Expression) -> Option> { + let elements = match e { + Expression::ArrayExpression(a) => a, + Expression::NewExpression(n) => { + // `new Uint8Array([...])` — the sequence is the sole argument. `new + // Uint8Array(4)` (a LENGTH, not a value) deliberately does not match: it pins + // no bytes, and reading `4` as the byte 0x04 would invent a constraint. + match arg_expr(n.arguments.first()?)? { + Expression::ArrayExpression(a) => a, + _ => return None, + } + } + _ => return None, + }; + elements + .elements + .iter() + .map(|el| match el.as_expression()? { + Expression::NumericLiteral(n) if (0.0..=255.0).contains(&n.value) => { + Some(n.value as u8) + } + _ => None, + }) + .collect() +} + #[cfg(test)] mod tests { use super::*; diff --git a/generated/incoming/index.json b/generated/incoming/index.json index 1c0d517..f23a1f3 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -26,7 +26,29 @@ "method": "attrEnumValues", "name": "type", "type": "enum", - "required": true + "required": true, + "enumRef": { + "name": "CiphertextType", + "module": "WAWebBackendJobs.flow", + "variants": [ + { + "name": "Skmsg", + "value": "skmsg" + }, + { + "name": "Pkmsg", + "value": "pkmsg" + }, + { + "name": "Msg", + "value": "msg" + }, + { + "name": "Msmsg", + "value": "msmsg" + } + ] + } }, { "method": "maybeAttrString", @@ -111,7 +133,36 @@ "enumRef": { "name": "STANZA_MSG_TYPES", "module": "WAWebHandleMsgCommon", - "variants": [] + "variants": [ + { + "name": "text", + "value": "text" + }, + { + "name": "media", + "value": "media" + }, + { + "name": "medianotify", + "value": "medianotify" + }, + { + "name": "pay", + "value": "pay" + }, + { + "name": "poll", + "value": "poll" + }, + { + "name": "reaction", + "value": "reaction" + }, + { + "name": "event", + "value": "event" + } + ] } }, { @@ -302,7 +353,18 @@ "method": "maybeAttrEnum", "name": "type", "type": "enum", - "required": false + "required": false, + "enumKeys": [ + "delivery", + "read", + "played", + "inactive", + "server-error", + "sender", + "read-self", + "played-self", + "peer_msg" + ] }, { "method": "attrUserJid", @@ -423,6 +485,21 @@ } ] }, + { + "method": "child", + "name": "id", + "type": "string", + "required": true, + "tag": "id", + "children": [ + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + } + ] + }, { "method": "child", "name": "value", @@ -473,7 +550,14 @@ "type": "string", "required": true, "tag": "registration", - "children": [] + "children": [ + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + } + ] } ] } @@ -2570,7 +2654,16 @@ "enumRef": { "name": "ALL_NONE", "module": "WAWebPrivacySettings", - "variants": [] + "variants": [ + { + "name": "all", + "value": "all" + }, + { + "name": "none", + "value": "none" + } + ] } } ] diff --git a/generated/iq/index.json b/generated/iq/index.json index b3a9c1b..4beaf5b 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -4473,6 +4473,36 @@ "tag": "digest", "children": [] }, + { + "method": "child", + "name": "registration", + "type": "string", + "required": true, + "tag": "registration", + "children": [ + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + } + ] + }, + { + "method": "child", + "name": "type", + "type": "string", + "required": true, + "tag": "type", + "children": [ + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + } + ] + }, { "method": "child", "name": "identity", @@ -4489,6 +4519,21 @@ } ] }, + { + "method": "child", + "name": "id", + "type": "string", + "required": true, + "tag": "id", + "children": [ + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + } + ] + }, { "method": "child", "name": "value", @@ -4521,6 +4566,12 @@ } ] }, + { + "method": "contentUint", + "name": "content", + "type": "integer", + "required": true + }, { "method": "child", "name": "hash", @@ -4863,6 +4914,8 @@ "name": "typeElementValue", "type": "bytes", "required": true, + "byteLength": 1, + "literalValue": "05", "sourcePath": [ "type" ] @@ -5310,6 +5363,8 @@ "name": "typeElementValue", "type": "bytes", "required": true, + "byteLength": 1, + "literalValue": "05", "sourcePath": [ "type" ] @@ -6120,6 +6175,8 @@ "name": "typeElementValue", "type": "bytes", "required": true, + "byteLength": 1, + "literalValue": "05", "sourcePath": [ "type" ] @@ -6541,6 +6598,8 @@ "name": "typeElementValue", "type": "bytes", "required": true, + "byteLength": 1, + "literalValue": "05", "sourcePath": [ "type" ] @@ -8961,7 +9020,11 @@ "method": "attrEnum", "name": "type", "type": "enum", - "required": true + "required": true, + "enumKeys": [ + "error", + "result" + ] }, { "method": "child", diff --git a/generated/manifest.json b/generated/manifest.json index 3142839..f80e581 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -8,7 +8,7 @@ "errorArms": 645, "errorTexts": 38, "fieldEnumRefs": 150, - "fieldLiterals": 1782, + "fieldLiterals": 1784, "referenceConstraints": 584, "typedErrorVariants": 104 }, @@ -56,12 +56,12 @@ "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "0c0f164b1fabd803d44fcc5b8f76e4288a04727066f344608662482b76ec6285" + "sha256": "f6714888d1385cd244830338821e0529240eddb7296f588ef59abceacfe6e174" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "4b862f391074732ad286fe4815e0dde7332f80c8624ca603c73b72c57cecaa12" + "sha256": "644607df3585822a944fe2dee3a08a7456d5cd00a7e5f07440a340051470dc86" }, "mex": { "file": "mex/index.json", @@ -71,7 +71,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "6504509e29cb7b50790f7cf12434b9a75f22843b4240a4cb5423e0e645a9f926" + "sha256": "a4f868868e9689488e4dcbf58ebb09665421626acdbef1ee9ceab27d4f946883" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index d158027..db24569 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -1341,7 +1341,12 @@ "method": "attrEnumValues", "name": "mediatype", "type": "enum", - "required": true + "required": true, + "enumKeys": [ + "image", + "video", + "text" + ] }, { "method": "contentBytes", @@ -1374,7 +1379,12 @@ "method": "attrEnumValues", "name": "mediatype", "type": "enum", - "required": true + "required": true, + "enumKeys": [ + "image", + "video", + "text" + ] }, { "method": "contentBytes", @@ -2854,7 +2864,11 @@ "method": "attrEnum", "name": "type", "type": "enum", - "required": true + "required": true, + "enumKeys": [ + "contacts", + "w:growth" + ] }, { "method": "child", diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index a6cd8e3..616f179 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -262,7 +262,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire). On a\n[`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding\n[`WapContent::const_bytes`] already uses on the request side —\nand [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))`\n→ `literalValue: \"05\"`, `byteLength: 1`.\n\n[`byte_length`]: ParsedField::byte_length\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index ecc35f7..6eb4ce3 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -327,7 +327,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire). On a\n[`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding\n[`WapContent::const_bytes`] already uses on the request side —\nand [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))`\n→ `literalValue: \"05\"`, `byteLength: 1`.\n\n[`byte_length`]: ParsedField::byte_length\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index a06168a..2c8a3dd 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -437,7 +437,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire). On a\n[`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding\n[`WapContent::const_bytes`] already uses on the request side —\nand [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))`\n→ `literalValue: \"05\"`, `byteLength: 1`.\n\n[`byte_length`]: ParsedField::byte_length\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 5188131..58fccf7 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -229,7 +229,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire). On a\n[`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding\n[`WapContent::const_bytes`] already uses on the request side —\nand [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))`\n→ `literalValue: \"05\"`, `byteLength: 1`.\n\n[`byte_length`]: ParsedField::byte_length\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index 9674712..7e93588 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -236,7 +236,7 @@ "format": "int64" }, "literalValue": { - "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire).\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", + "description": "The fixed value the parser pins this field to — `literal(attrString, participant,\n\"type\", \"admin\")` → `\"admin\"`, `literal(attrInt, error, \"code\", 429)` → `\"429\"`.\nAlways the string form of the literal; [`field_type`] says how to read it (a\n`code` field with `literalValue: \"429\"` is the integer 429 on the wire). On a\n[`ParsedFieldType::Bytes`] field it is **lowercase hex** — the encoding\n[`WapContent::const_bytes`] already uses on the request side —\nand [`byte_length`] is its length: `contentLiteralBytes(node, new Uint8Array([5]))`\n→ `literalValue: \"05\"`, `byteLength: 1`.\n\n[`byte_length`]: ParsedField::byte_length\n\n[`required`] tells the two pinning forms apart, and the difference matters to an\nemitter: a **required** literal (`literal`) is a hard discriminator — the\nattribute MUST be present and equal to this value or the variant does not match;\nan **optional** one (`optionalLiteral`) is pinned only when present — the emitter\nmay omit the attribute, but must never send a contradicting value.\n\n**On a [`ParsedFieldType::Bool`] field the pin is always conditional.** Such a\nfield is a *presence flag* — `{hasListCDhash: m.value != null}` — so its\n`required: true` describes the flag (the parser always produces it), never the\nwire attribute named by [`wire_name`], which by construction may be absent. The\npin constrains that attribute when present, and never the boolean itself.\n\n[`field_type`]: ParsedField::field_type\n[`required`]: ParsedField::required\n[`wire_name`]: ParsedField::wire_name", "type": [ "string", "null" From b111ce056b1ee72105d7126c198f4a899154ff7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 06:49:48 -0300 Subject: [PATCH 20/37] scan: count what the non-IQ domains lose, and fix the paths round 15 opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 16 — twelve findings. Ten are consequences of round 15's own changes. Routing every typed content accessor into field extraction was half a fix: on the legacy path `field_from_call` read the argument of plain `contentBytes` and nothing else, so `contentLiteralBytes` and `contentBytesRange` arrived and published unconstrained bytes. Both now pin what they pin, and an unresolvable sequence is counted rather than published as free bytes. `static_byte_literal` documented `Uint8Array.of(1, 2)` and never matched it — every call expression fell through to "unresolved". It also admitted `1.5`, which `as u8` truncated to a pinned byte the source never states. Both fixed, and the helper is now shared with the legacy scanner so one spelling cannot mean two things. An enum table with a non-literal member published the remaining literals as the complete set: `{A: "a", B: computed}` became an enum rejecting `B` while the runtime accepts it. Refused whole, like the cross-module resolver already does. The new `PendingEnum` distinction only works if someone reads it, and incoming and notif dropped the linker without reading its losses. Both domains now report `dropsByReason` in the manifest — incoming has 4 — and the legacy scanner's other unresolved constraints ride out on the shape it produces, since it has no diagnostics channel of its own. The IQ drop reason is a shared constant rather than two literals, and the `contentLiteralBytes` loss is keyed per occurrence like every other reason. Three control-flow paths round 15 left half-done, all latent, all tested: * `case "a": if (cond) break; return X;` inside a nested switch — the `break` leaves only the inner switch and control resumes in the outer case, but the later `return` made `any` call the whole list terminating. Order matters, so a statement that can escape upward now poisons the conclusion before later statements are consulted. * an assignment inside a branch updated only the cloned scope, so `var j = jid; if (c) j = lid; return {id: j}` published `jid` as certain. Tombstoned where the branches rejoin. * the minifier writes runs of assignments as one comma sequence, which the new reassignment arm skipped entirely. Each of the five new behaviours was confirmed to fail without its fix. 528 tests, 12/12 schemas, determinism check clean, reference codegen byte-identical. Only the manifest moved: +2 diagnostics blocks. --- crates/wa-ir/src/iq.rs | 16 ++- crates/wa-notif/src/actions.rs | 168 +++++++++++++++++++++++++--- crates/wa-notif/src/lib.rs | 34 ++++-- crates/wa-notif/src/tests.rs | 77 ++++++++++++- crates/wa-scan/src/enum_link.rs | 40 ++++++- crates/wa-scan/src/incoming.rs | 15 ++- crates/wa-scan/src/lib.rs | 5 +- crates/wa-scan/src/response.rs | 145 +++++++++++++++++++++--- crates/wa-scan/src/response_smax.rs | 55 +++++++-- crates/whatspec/src/main.rs | 50 +++++++-- generated/manifest.json | 6 + 11 files changed, 548 insertions(+), 63 deletions(-) diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index e3cc195..193aad2 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -410,7 +410,9 @@ pub enum PendingEnum { /// `o("Mod").ENUM` — a module export the bundle-wide index can finish resolving. Link(AttrEnumRef), /// A table that is not a structurally recoverable reference (a computed expression, - /// a runtime-composed set). Carries the wire attribute it constrains, for the report. + /// a runtime-composed set, an object literal with a non-literal member). A unit + /// variant: the reporting pass names the loss from the field's own `wireName`/`name`, + /// so there is nothing to carry here. Unresolvable, } @@ -693,6 +695,18 @@ pub struct ParsedResponse { pub fields: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub variants: Vec, + /// Scanner-internal: constraints this parser carried that could not be resolved + /// structurally, as `dropsByReason` keys. Never serialized. + /// + /// The legacy scanner reads one module at a time and has no diagnostics channel of + /// its own, so it parks the losses on the shape it produces and whoever finishes the + /// shape drains them ([`crate::wap`]'s consumers do this via the response enum + /// linker). It exists for the same reason [`ParsedField::pending_enum_ref`] does: a + /// constraint seen and not recovered must never be indistinguishable from no + /// constraint at all. + #[serde(skip)] + #[cfg_attr(feature = "schema", schemars(skip))] + pub pending_drops: Vec, } // ─── IQ operation ──────────────────────────────────────────────────────────────── diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index b144bac..556efc6 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -495,7 +495,44 @@ fn fall_through_body<'b, 'a>( /// leaves a path open and must fall through, while `switch (t) { case "a": return X; /// default: return Y }` closes them all and must not. fn terminates(stmts: &[Statement]) -> bool { - stmts.iter().any(|s| stmt_exits(s, false)) + list_exits(stmts, false) +} + +/// Whether the list leaves the enclosing case on every path. +/// +/// Order matters, and `any` got it wrong: in `case X: if (cond) break; return A;` inside a +/// nested switch, the `break` leaves only the INNER switch and control resumes in the +/// outer case, yet the later `return` made the whole list look terminating. A statement +/// that can escape upward poisons the conclusion before any later statement is consulted. +fn list_exits(stmts: &[Statement], nested: bool) -> bool { + for s in stmts { + if can_escape(s, nested) { + return false; + } + if stmt_exits(s, nested) { + return true; + } + } + false +} + +/// Whether some path through `s` reaches a `break` that leaves the construct we are +/// analysing rather than ending the case. Only meaningful inside a nested breakable +/// construct — at case level a `break` *is* the exit. +fn can_escape(s: &Statement, nested: bool) -> bool { + if !nested { + return false; + } + match s { + Statement::BreakStatement(_) => true, + Statement::BlockStatement(b) => b.body.iter().any(|s| can_escape(s, nested)), + Statement::IfStatement(i) => { + can_escape(&i.consequent, nested) + || i.alternate.as_ref().is_some_and(|a| can_escape(a, nested)) + } + // A deeper switch or loop consumes the `break` itself, so it cannot reach us. + _ => false, + } } /// Whether `s` exits the enclosing switch case on every path. @@ -507,7 +544,7 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { match s { Statement::ReturnStatement(_) | Statement::ThrowStatement(_) => true, Statement::BreakStatement(_) | Statement::ContinueStatement(_) => !nested, - Statement::BlockStatement(b) => b.body.iter().any(|s| stmt_exits(s, nested)), + Statement::BlockStatement(b) => list_exits(&b.body, nested), // Both arms, or the statement after the `if` still runs. Statement::IfStatement(i) => i .alternate @@ -517,9 +554,10 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { // so it does not have to exit on its own. Statement::SwitchStatement(sw) => { sw.cases.iter().any(|c| c.test.is_none()) - && sw.cases.iter().all(|c| { - c.consequent.is_empty() || c.consequent.iter().any(|s| stmt_exits(s, true)) - }) + && sw + .cases + .iter() + .all(|c| c.consequent.is_empty() || list_exits(&c.consequent, true)) } _ => false, } @@ -701,15 +739,21 @@ fn collect_returns<'b, 'a>( // An assignment whose right-hand side is not a wire read REMOVES the binding // rather than leaving the old one: the name no longer holds what it held, and // reporting a value that has been overwritten is worse than reporting none. + // The minifier also writes runs of assignments as one comma sequence + // (`a = t.attrString("jid"), b = t.attrInt("n")`), which arrives as a + // `SequenceExpression`; matching only a bare assignment skipped those + // entirely and left the stale initializer installed. Statement::ExpressionStatement(e) => { - if let Expression::AssignmentExpression(a) = &e.expression - && let Some(name) = a.left.get_identifier_name() - { - let reads_wire = find_accessor(strip_guard(&a.right), &scope).is_some(); - if reads_wire { - scope.insert(name, &a.right); - } else { - scope.remove(name); + let mut ops = Vec::new(); + flatten_sequence(&e.expression, &mut ops); + for a in ops { + if let Some(name) = a.left.get_identifier_name() { + let reads_wire = find_accessor(strip_guard(&a.right), &scope).is_some(); + if reads_wire { + scope.insert(name, &a.right); + } else { + scope.remove(name); + } } } } @@ -726,8 +770,18 @@ fn collect_returns<'b, 'a>( if let Some(alt) = &i.alternate { collect_returns(&[alt], &scope, out); } + // Where the branches rejoin, a name either branch reassigned no longer has + // one known value: `var x = attrString("jid"); if (c) x = attrString("lid"); + // return {id: x}` reads `lid` on one legal path, and keeping the pre-branch + // binding published `jid` as if it were certain. Tombstoned rather than + // guessed, the same way a key bound to two different reads is refused. + tombstone_branch_writes(s, &mut scope); + } + Statement::TryStatement(t) => { + collect_returns(&as_refs(&t.block.body), &scope, out); + // A `try` body may have run in part; nothing it wrote is certain after it. + tombstone_branch_writes(s, &mut scope); } - Statement::TryStatement(t) => collect_returns(&as_refs(&t.block.body), &scope, out), // A nested `switch` inside an arm (or a helper body) is how THAT arm picks // its shape — `case LINK: switch (linkType) { case "parent": return {…}; // default: return {…} }` describes two legal actions for `link`, and skipping @@ -738,12 +792,98 @@ fn collect_returns<'b, 'a>( for c in &sw.cases { collect_returns(&as_refs(&c.consequent), &scope, out); } + tombstone_branch_writes(s, &mut scope); } _ => {} } } } +/// The assignment operands of an expression, flattening the minifier's comma sequences. +fn flatten_sequence<'b, 'a>( + e: &'b Expression<'a>, + out: &mut Vec<&'b oxc_ast::ast::AssignmentExpression<'a>>, +) { + match e { + Expression::AssignmentExpression(a) => out.push(a), + Expression::SequenceExpression(seq) => { + for sub in &seq.expressions { + flatten_sequence(sub, out); + } + } + Expression::ParenthesizedExpression(p) => flatten_sequence(&p.expression, out), + _ => {} + } +} + +/// Drop every binding a conditional construct may have reassigned, so statements after +/// the rejoin do not read a value that only one path wrote. +fn tombstone_branch_writes(s: &Statement, scope: &mut Scope) { + let mut names = Vec::new(); + assigned_names(s, &mut names); + for n in names { + scope.remove(n); + } +} + +/// Every identifier assigned anywhere inside `s`, not descending into nested functions +/// (whose assignments belong to their own scope, not this one). +fn assigned_names<'a>(s: &'a Statement, out: &mut Vec<&'a str>) { + fn expr<'a>(e: &'a Expression, out: &mut Vec<&'a str>) { + match e { + Expression::AssignmentExpression(a) => { + if let Some(n) = a.left.get_identifier_name() { + out.push(n); + } + expr(&a.right, out); + } + Expression::SequenceExpression(seq) => { + seq.expressions.iter().for_each(|e| expr(e, out)) + } + Expression::ParenthesizedExpression(p) => expr(&p.expression, out), + Expression::ConditionalExpression(c) => { + expr(&c.test, out); + expr(&c.consequent, out); + expr(&c.alternate, out); + } + Expression::LogicalExpression(l) => { + expr(&l.left, out); + expr(&l.right, out); + } + _ => {} + } + } + match s { + Statement::ExpressionStatement(e) => expr(&e.expression, out), + Statement::VariableDeclaration(d) => { + for decl in &d.declarations { + if let (Some(n), Some(_)) = (decl.id.get_identifier_name(), decl.init.as_ref()) { + out.push(n.as_str()); + } + } + } + Statement::BlockStatement(b) => b.body.iter().for_each(|s| assigned_names(s, out)), + Statement::IfStatement(i) => { + expr(&i.test, out); + assigned_names(&i.consequent, out); + if let Some(a) = &i.alternate { + assigned_names(a, out); + } + } + Statement::TryStatement(t) => t.block.body.iter().for_each(|s| assigned_names(s, out)), + Statement::SwitchStatement(sw) => sw + .cases + .iter() + .for_each(|c| c.consequent.iter().for_each(|s| assigned_names(s, out))), + Statement::ReturnStatement(r) => { + if let Some(a) = r.argument.as_ref() { + expr(a, out); + } + } + _ => {} + } +} + fn collect_branches<'b, 'a>(e: &'b Expression<'a>, out: &mut Vec<&'b Expression<'a>>) { match e { Expression::ConditionalExpression(c) => { diff --git a/crates/wa-notif/src/lib.rs b/crates/wa-notif/src/lib.rs index 9931efd..a2a8c8c 100644 --- a/crates/wa-notif/src/lib.rs +++ b/crates/wa-notif/src/lib.rs @@ -73,6 +73,17 @@ pub fn extract_notif_from_modules( module_defs: &[ModuleDefinition], wa_version: &str, ) -> NotifIr { + extract_notif_with_diagnostics(source, module_defs, wa_version).0 +} + +/// Like [`extract_notif_from_modules`], but also returns the constraints the handlers' +/// legacy parsers carried and this domain could not resolve, by `dropsByReason` key. +/// See [`wa_scan::scan_incoming_with_diagnostics`] for why they are counted. +pub fn extract_notif_with_diagnostics( + source: &str, + module_defs: &[ModuleDefinition], + wa_version: &str, +) -> (NotifIr, std::collections::BTreeMap) { // Collect each dispatcher once (a module is often defined in several shards). let mut dispatchers: Vec<(String, Dispatch)> = Vec::new(); let mut seen = std::collections::HashSet::new(); @@ -182,17 +193,26 @@ pub fn extract_notif_from_modules( if n.content.is_none() && let Some(shape) = srvreq_shapes.get(&n.notif_type) { - n.content = Some(shape.clone()); + let mut shape = shape.clone(); + // This fallback runs after the pass above, so its shapes get resolved + // here — otherwise a link recovered from a server-request read-shape + // would be the one kind that still shipped unfinished. + enums.resolve(&format!("srvreq::{}", n.notif_type), &mut shape); + n.content = Some(shape); } } } - NotifIr { - wa_version: wa_version.to_string(), - dispatcher_modules, - stanza_tags: dispatch.stanza_tags, - notifications: dispatch.notifications, - } + let drops = enums.into_drop_counts(); + ( + NotifIr { + wa_version: wa_version.to_string(), + dispatcher_modules, + stanza_tags: dispatch.stanza_tags, + notifications: dispatch.notifications, + }, + drops, + ) } /// Fold one dispatcher's arms into the accumulated catalog. New tags/types are diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index dde4ea1..a4451aa 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1254,6 +1254,23 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, who:t.mapChildrenWithTag("p", function(p){ return p.hasChild("z") ? p.attrString("jid") : p.attrString("lid"); })}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.HELPER: return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST}, hlp(t)); + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.ESCAPE: + switch (t.attrString("k")) { + case "a": if (t.hasChild("q")) break; return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, e1:t.attrString("e1")}; + default: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, e2:t.attrString("e2")}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.TAIL: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, t1:t.attrString("t1")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.JOIN: { + var j=t.attrString("jid"); + if (t.hasChild("alt")) j=t.attrString("lid"); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:j}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SEQ: { + var s1, s2; + s1=t.attrString("jid"), s2=t.attrInt("n"); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:s1, n:s2}; + } } }); return {actions:x}; @@ -1339,6 +1356,13 @@ fn a_bare_callback_read_is_optional_when_only_one_branch_takes_it() { .iter() .find(|c| c.name == "who") .expect("the mapped child is recovered"); + // Asserted before the requiredness check: `all` on an empty list is vacuously true, + // and this is exactly the path that could regress to recovering no fields at all. + let names: Vec<&str> = child.fields.iter().map(|f| f.wire_name.as_str()).collect(); + assert!( + names.contains(&"jid") && names.contains(&"lid"), + "both alternative reads survive the fold: {names:?}" + ); assert!( child.fields.iter().all(|f| !f.required), "a read taken on only one branch is not required: {:?}", @@ -1359,3 +1383,54 @@ fn a_helper_operand_replaces_the_earlier_action_type() { "the later operand wins, `actionType` included" ); } + +#[test] +fn a_nested_break_path_keeps_the_outer_fall_through() { + // `case "a": if (cond) break; return FIRST;` inside a nested switch: the `break` + // leaves only the INNER switch, so control resumes in the outer `escape` case and + // falls through to `tail`. Treating the later `return` as proof that every path + // exited hid that action. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "escape") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert!( + got.contains(&"third"), + "the break path falls through to the next outer case: {got:?}" + ); +} + +#[test] +fn a_binding_reassigned_in_one_branch_is_not_reported_as_certain() { + // `var j = attrString("jid"); if (c) j = attrString("lid"); return {id: j}` reads + // `lid` on one legal path. Keeping the pre-branch binding published `jid` as fact. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "join"); + assert!( + !a.fields + .iter() + .any(|f| f.name == "id" && f.wire_name == "jid"), + "the branch write must not leave `jid` asserted: {:?}", + a.fields + ); +} + +#[test] +fn a_comma_sequence_of_assignments_binds_every_operand() { + // The minifier writes runs of assignments as one `SequenceExpression` + // (`s1 = t.attrString("jid"), s2 = t.attrInt("n")`); matching only a bare assignment + // statement skipped them all. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "seq"); + let wire = |name: &str| { + a.fields + .iter() + .find(|f| f.name == name) + .map(|f| f.wire_name.as_str()) + }; + assert_eq!(wire("id"), Some("jid")); + assert_eq!(wire("n"), Some("n")); +} diff --git a/crates/wa-scan/src/enum_link.rs b/crates/wa-scan/src/enum_link.rs index bc1759a..92271a0 100644 --- a/crates/wa-scan/src/enum_link.rs +++ b/crates/wa-scan/src/enum_link.rs @@ -202,8 +202,12 @@ fn variants_of(def: wa_ir::InternalEnumDef) -> Option> { /// their artifacts shipped links that resolved to nothing. pub struct ResponseEnumLinker<'a> { inner: EnumResolver<'a>, - /// Losses keyed `site\x01module.ENUM@attr`; see [`EnumResolver::resolve_fields`]. + /// Enum-link losses keyed `site\x01module.ENUM@attr`; see + /// [`EnumResolver::resolve_fields`]. dropped: std::collections::BTreeSet, + /// Everything else the legacy scanner could not resolve, keyed the same way so two + /// occurrences in one parser count twice and the same one seen twice counts once. + other: std::collections::BTreeSet, } impl<'a> ResponseEnumLinker<'a> { @@ -211,6 +215,7 @@ impl<'a> ResponseEnumLinker<'a> { Self { inner: EnumResolver::new(defs, source), dropped: Default::default(), + other: Default::default(), } } @@ -222,12 +227,43 @@ impl<'a> ResponseEnumLinker<'a> { self.inner .resolve_fields(site, &mut v.fields, &mut self.dropped); } + // The legacy scanner parks its other unresolved constraints (a byte pin, a + // length band) on the shape because it has no diagnostics channel of its own. + // This is the one pass every domain runs, so it is where they get collected. + for d in shape.pending_drops.drain(..) { + self.other.insert(format!("{site}\u{1}{d}")); + } } - /// The links seen but not resolvable, for `dropsByReason`. + /// The enum links seen but not resolvable, for `dropsByReason`. pub fn into_drops(self) -> std::collections::BTreeSet { self.dropped } + + /// Every loss this pass collected, grouped by `dropsByReason` key: the enum links + /// under [`crate::response_smax::ENUM_DROP`], plus whatever the legacy scanner + /// parked on the shapes, each already de-duplicated per site. + pub fn into_drop_counts(self) -> std::collections::BTreeMap { + let mut out: std::collections::BTreeMap = Default::default(); + if !self.dropped.is_empty() { + out.insert( + crate::response_smax::ENUM_DROP.to_string(), + self.dropped.len(), + ); + } + for key in self.other { + // `site\x01reason@detail` → the reason is what a reader groups by. + let reason = key + .split_once('\u{1}') + .map_or(key.as_str(), |(_, r)| r) + .split('@') + .next() + .unwrap_or("") + .to_string(); + *out.entry(reason).or_default() += 1; + } + out + } } #[cfg(test)] diff --git a/crates/wa-scan/src/incoming.rs b/crates/wa-scan/src/incoming.rs index 7fd5ad2..56a1d35 100644 --- a/crates/wa-scan/src/incoming.rs +++ b/crates/wa-scan/src/incoming.rs @@ -41,6 +41,19 @@ fn incoming_tag(tag: &str) -> Option { /// `new WADeprecatedWapParser("…", fn)` whose body asserts a content tag, with its /// field tree recovered. pub fn scan_incoming_from_modules(source: &str, defs: &[ModuleDefinition]) -> Vec { + scan_incoming_with_diagnostics(source, defs).0 +} + +/// Like [`scan_incoming_from_modules`], but also returns the constraints the legacy +/// parsers carried and this domain could not resolve, by `dropsByReason` key. +/// +/// The counts existed only for IQ, so an unresolvable enum table in a received-stanza +/// parser produced a field with no legal values and no signal anywhere that a constraint +/// had been lost — exactly the distinction the pending marker was introduced to keep. +pub fn scan_incoming_with_diagnostics( + source: &str, + defs: &[ModuleDefinition], +) -> (Vec, std::collections::BTreeMap) { let mut out = Vec::new(); let mut seen = HashSet::new(); // The legacy parser records `o("Mod").ENUM` as a pending link because it reads one @@ -88,7 +101,7 @@ pub fn scan_incoming_from_modules(source: &str, defs: &[ModuleDefinition]) -> Ve out.dedup_by(|a, b| { a.tag == b.tag && a.module == b.module && a.shape.parser_name == b.shape.parser_name }); - out + (out, enums.into_drop_counts()) } /// A `WASmaxIn*Response{Success,Negative}` module whose root reads a top-level `` — diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index 6706bb6..c4c8abc 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -24,7 +24,7 @@ mod srvreq; mod stanza; pub use enum_link::ResponseEnumLinker; -pub use incoming::scan_incoming_from_modules; +pub use incoming::{scan_incoming_from_modules, scan_incoming_with_diagnostics}; pub use mixin_index::MixinIndex; pub use module::{CrossModuleStat, DropReason, scan_module_outcome, scan_module_source}; pub use response::parse_module_wap_parsers; @@ -307,8 +307,7 @@ pub fn scan_iq_with_diagnostics( constraint_drops: { let mut d = response_index.drop_counts(); if !enum_drops.is_empty() { - *d.entry("response enum argument not structurally resolvable".to_string()) - .or_default() += enum_drops.len(); + *d.entry(response_smax::ENUM_DROP.to_string()).or_default() += enum_drops.len(); } d }, diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index d8a1713..c4581a8 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -61,6 +61,7 @@ pub(crate) fn parsed_response_from_new_expr( parser_name: name, assertions: result.assertions, fields: result.fields, + pending_drops: result.unresolved, ..Default::default() }) } @@ -158,6 +159,8 @@ impl<'a> Visit<'a> for ParserCollector<'_> { pub(crate) struct ParserResult { pub assertions: Vec, pub fields: Vec, + /// See [`ParsedResponse::pending_drops`]. + pub unresolved: Vec, } /// Accessors that read a value off a node, as the parser treats them: every shared @@ -286,7 +289,12 @@ fn strip_members_call<'b, 'a>(e: &'b Expression<'a>) -> &'b Expression<'a> { .unwrap_or(e) } -fn field_from_call(method: &str, call: &CallExpression, module_scope: &ModuleScope) -> ParsedField { +fn field_from_call( + method: &str, + call: &CallExpression, + module_scope: &ModuleScope, + unresolved: &mut Vec, +) -> ParsedField { let arg0 = call.arguments.first().and_then(arg_expr); // A content accessor reads the element's content and names no attribute, so its // first argument is never the field name — `contentEnum(TABLE)` would otherwise be @@ -302,10 +310,41 @@ fn field_from_call(method: &str, call: &CallExpression, module_scope: &ModuleSco method_to_field_type(method), is_method_required(method), ); - if method == wap::CONTENT_BYTES - && let Some(Expression::NumericLiteral(n)) = arg0 - { - f.byte_length = Some(n.value as u32); + // Each byte accessor pins something different, and routing them all here without + // reading their arguments published unconstrained `bytes` — an emitter would think + // any payload passes where the parser accepts one sequence or one length band. + match method { + wap::CONTENT_BYTES => { + if let Some(Expression::NumericLiteral(n)) = arg0 { + f.byte_length = Some(n.value as u32); + } + } + // `contentLiteralBytes(new Uint8Array([5]))` — the node is the receiver on this + // path, so the sequence is argument 0 (smax passes the node first). + "contentLiteralBytes" => match arg0.and_then(crate::response_smax::static_byte_literal) { + Some(bytes) => { + f.byte_length = Some(bytes.len() as u32); + f.literal_value = Some(bytes.iter().map(|b| format!("{b:02x}")).collect()); + } + None => unresolved.push(format!("contentLiteralBytes@{field_name}")), + }, + // `contentBytesRange(min, max)` — a payload-size band. `min == max` is a fixed + // length, which `byte_length` already expresses. + "contentBytesRange" => { + let bound = |i: usize| match call.arguments.get(i).and_then(arg_expr) { + Some(Expression::NumericLiteral(n)) => Some(n.value as u32), + _ => None, + }; + match (bound(0), bound(1)) { + (Some(min), Some(max)) if min == max => f.byte_length = Some(min), + (Some(min), Some(max)) => { + f.byte_min = Some(min); + f.byte_max = Some(max); + } + _ => unresolved.push(format!("contentBytesRange@{field_name}")), + } + } + _ => {} } match pending_enum_ref(method, call, module_scope) { EnumSource::Pending(er) => f.pending_enum_ref = Some(wa_ir::PendingEnum::Link(er)), @@ -382,6 +421,7 @@ fn analyze_with_scope(code: &str, param: &str, module: &ModuleScope) -> ParserRe fields: Vec::new(), child_vars: HashMap::new(), pending_enum_keys: HashMap::new(), + unresolved: Vec::new(), helper_depth: 0, }; a.visit_program(&ret.program); @@ -389,6 +429,7 @@ fn analyze_with_scope(code: &str, param: &str, module: &ModuleScope) -> ParserRe ParserResult { assertions: a.assertions, fields: a.fields, + unresolved: a.unresolved, } } @@ -408,6 +449,10 @@ struct ParserAnalyzer<'src, 'ms> { /// (the map is module-scope, so it's resolved and stashed here, then attached to the /// matching field in a post-pass — order-independent of the plain read of the attr). pending_enum_keys: HashMap>, + /// Constraints seen on this parser but not statically resolvable, as + /// `dropsByReason` keys. Parked on the produced [`ParsedResponse`] for whoever + /// finishes it, since the legacy scanner has no diagnostics channel of its own. + unresolved: Vec, /// Recursion guard for module-scope helper descent (`m(n,i)` → analyze `m`'s body). helper_depth: u32, } @@ -518,7 +563,12 @@ impl ParserAnalyzer<'_, '_> { // ── Attr/content accessor on the param directly ── if is_value_method(method) && obj_is_param { - self.fields.push(field_from_call(method, call, self.module)); + self.fields.push(field_from_call( + method, + call, + self.module, + &mut self.unresolved, + )); return; } @@ -535,7 +585,7 @@ impl ParserAnalyzer<'_, '_> { push_child_field( &mut self.fields, idx, - field_from_call(method, call, self.module), + field_from_call(method, call, self.module, &mut self.unresolved), ); return; } @@ -596,7 +646,7 @@ impl ParserAnalyzer<'_, '_> { push_child_field( &mut self.fields, idx, - field_from_call(method, call, self.module), + field_from_call(method, call, self.module, &mut self.unresolved), ); } @@ -843,11 +893,17 @@ impl<'a> Visit<'a> for ModuleScopeBuilder<'_> { self.maps.entry(n.clone()).or_insert_with(|| { wa_oxc::obj_props(obj).map(|(k, _)| k.to_string()).collect() }); - self.members.entry(n).or_insert_with(|| { - wa_oxc::obj_props(obj) - .filter_map(|(_, v)| as_string_lit(v).map(str::to_string)) - .collect() - }); + // All-or-nothing, like the cross-module resolver's `variants_of`: + // `{A: "a", B: computed}` must not publish `["a"]` as the complete + // set — that is an enum the IR says rejects `B` when the runtime + // accepts it. An incomplete table is left absent and the accessor + // is counted as unresolved instead. + let members: Option> = wa_oxc::obj_props(obj) + .map(|(_, v)| as_string_lit(v).map(str::to_string)) + .collect(); + if let Some(members) = members.filter(|m| !m.is_empty()) { + self.members.entry(n).or_insert(members); + } } } } @@ -881,6 +937,7 @@ fn analyze_child_node( fields: Vec::new(), child_vars: HashMap::from([(node_param.to_string(), tag.to_string())]), pending_enum_keys: HashMap::new(), + unresolved: Vec::new(), helper_depth: depth, }; a.visit_program(&ret.program); @@ -1497,4 +1554,66 @@ mod tests { assert_eq!(f.pending_enum_ref, Some(wa_ir::PendingEnum::Unresolvable)); assert!(f.enum_keys.is_none() && f.enum_ref.is_none()); } + + #[test] + fn legacy_byte_accessors_keep_the_constraint_they_pin() { + // Routing the typed content accessors into field extraction is only half the job: + // `field_from_call` read the argument of plain `contentBytes` and nothing else, so + // these two published unconstrained bytes — an emitter would think any payload + // passes where the parser accepts one sequence, or one length band. + let r = analyze_parser_ast( + r#"{ e.child("t").contentLiteralBytes(Uint8Array.of(5, 255)); + e.child("blob").contentBytesRange(1, 128); }"#, + "e", + ); + let leaf = |tag: &str| { + r.fields + .iter() + .find(|f| f.tag.as_deref() == Some(tag)) + .and_then(|f| f.children.as_ref()) + .and_then(|c| c.first()) + .unwrap_or_else(|| panic!("no leaf under <{tag}>")) + .clone() + }; + let pinned = leaf("t"); + assert_eq!(pinned.literal_value.as_deref(), Some("05ff")); + assert_eq!(pinned.byte_length, Some(2)); + let ranged = leaf("blob"); + assert_eq!((ranged.byte_min, ranged.byte_max), (Some(1), Some(128))); + assert_eq!( + ranged.byte_length, None, + "a true range is not a fixed length" + ); + } + + #[test] + fn an_unresolvable_byte_pin_is_reported_not_published_as_free_bytes() { + let r = analyze_parser_ast(r#"{ e.child("t").contentLiteralBytes(computed()); }"#, "e"); + assert!( + r.unresolved + .iter() + .any(|d| d.starts_with("contentLiteralBytes@")), + "the loss is counted: {:?}", + r.unresolved + ); + } + + #[test] + fn a_partially_literal_enum_table_is_refused_whole() { + // `{A: "a", B: computed}` must not publish `["a"]` as the complete legal set — + // that is an enum the IR says rejects `B` while the runtime accepts it. + let module = r#"__d("M",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var u=n("$InternalEnum")({A:"a",B:pick()}); + var c=new(r("WADeprecatedWapParser"))("p", function(e){ + e.assertTag("message"); + e.attrEnumValues("k", u.members()); + return {}; + }); + }),1);"#; + let out = parse_module_wap_parsers(module); + let p = out.iter().find(|r| r.parser_name == "p").expect("parser"); + let f = p.fields.iter().find(|f| f.name == "k").expect("field"); + assert!(f.enum_keys.is_none(), "an incomplete table is not the set"); + assert_eq!(f.pending_enum_ref, Some(wa_ir::PendingEnum::Unresolvable)); + } } diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 4cbc78e..00638c5 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -47,7 +47,11 @@ use wa_ir::{ use wa_oxc::{arg_expr, as_call, as_identifier, as_int, as_string_lit, callee_method}; /// Drop reason for an enum accessor whose enum could not be resolved to its variants. -const ENUM_DROP: &str = "response enum argument not structurally resolvable"; +pub(crate) const ENUM_DROP: &str = "response enum argument not structurally resolvable"; + +/// The `dropsByReason` key for a `contentLiteralBytes` pin whose sequence is not a +/// compile-time constant. +const BYTES_DROP: &str = "contentLiteralBytes sequence not statically resolvable"; /// A module's local parser functions, keyed by name → re-parsable source. Child /// accessors reference these by identifier (`optionalChildWithTag(n, "x", parseX)`). @@ -722,10 +726,14 @@ fn classify_call( "contentLiteralBytes" => { let bytes = args.get(1).and_then(arg_expr).and_then(static_byte_literal); if bytes.is_none() { - resolver.drop_note_keyed( - "contentLiteralBytes sequence not statically resolvable", - site.to_string(), - ); + // Keyed per OCCURRENCE, like every other reason here: a parser with two + // unresolvable byte pins has lost two constraints, and `site` alone + // folded them into one. The node path is what tells them apart. + let occurrence = source_path + .as_ref() + .map(|p| p.join("/")) + .unwrap_or_else(|| "".to_string()); + resolver.drop_note_keyed(BYTES_DROP, format!("{site}:{occurrence}")); } Binding::Field { method: wap::CONTENT_BYTES.to_string(), @@ -2035,7 +2043,22 @@ impl Resolver<'_> { /// A compile-time byte sequence: `new Uint8Array([5])`, `Uint8Array.of(1, 2)`, or a bare /// `[0, 1]`. `None` for anything computed — the caller counts that as a dropped /// constraint rather than publishing the field as unconstrained. -fn static_byte_literal(e: &Expression) -> Option> { +/// +/// Shared with the legacy scanner, which reaches `contentLiteralBytes` by a different +/// route and must pin the same value from the same spelling. +pub(crate) fn static_byte_literal(e: &Expression) -> Option> { + // `Uint8Array.of(1, 2)` — the bytes are the ARGUMENTS, not an array operand. It was + // documented as supported here and was not: every call expression fell through to + // `None` and the pin was reported as an unresolved constraint. + if let Some(call) = wa_oxc::as_call(e) + && callee_method(call) == Some("of") + { + return call + .arguments + .iter() + .map(|a| arg_expr(a).and_then(byte_literal)) + .collect(); + } let elements = match e { Expression::ArrayExpression(a) => a, Expression::NewExpression(n) => { @@ -2052,15 +2075,23 @@ fn static_byte_literal(e: &Expression) -> Option> { elements .elements .iter() - .map(|el| match el.as_expression()? { - Expression::NumericLiteral(n) if (0.0..=255.0).contains(&n.value) => { - Some(n.value as u8) - } - _ => None, - }) + .map(|el| byte_literal(el.as_expression()?)) .collect() } +/// One numeric literal that is exactly a byte. Fractions are rejected rather than +/// truncated: `1.5 as u8` is `1`, a pinned value the source never states. +fn byte_literal(e: &Expression) -> Option { + match e { + Expression::NumericLiteral(n) + if n.value.fract() == 0.0 && (0.0..=255.0).contains(&n.value) => + { + Some(n.value as u8) + } + _ => None, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index e3fd03b..89faf66 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -1639,11 +1639,18 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou let (abprops_arts, abprops_count) = abprops.expect(checked); let (enums_arts, enums_count) = enums.expect(checked); let (wam_arts, wam_count) = wam.expect(checked); - let (notif_arts, (notif_count, notif_typed, notif_tags, notif_actions, notif_action_shapes)) = - notif.expect(checked); + let (notif_arts, notif_counts) = notif.expect(checked); + let NotifCounts { + types: notif_count, + typed_content: notif_typed, + stanza_tags: notif_tags, + actions: notif_actions, + action_shapes: notif_action_shapes, + drops: notif_drops, + } = notif_counts; let (stanza_arts, stanza_count) = stanza.expect(checked); let (tokens_arts, (token_single, token_double)) = tokens.expect(checked); - let (incoming_arts, incoming_count) = incoming.expect(checked); + let (incoming_arts, (incoming_count, incoming_drops)) = incoming.expect(checked); let (srvreq_arts, srvreq_count) = srvreq.expect(checked); let (wasm_arts, (wasm_binaries, wasm_resources, wasm_wasm_handles)) = wasm.expect(checked); @@ -1833,7 +1840,12 @@ fn build_artifacts(wa_version: &str, source: &str) -> Result<(Vec, Cou "stanzaTags": counts.notif_stanza_tags, "actions": counts.notif_actions, "actionShapes": counts.notif_action_shapes, + "dropsByReason": notif_drops, }, + // These two domains run the same legacy parser as IQ and had no diagnostics + // block at all, so a constraint they saw and could not recover was reported + // nowhere — the one case the pending marker cannot distinguish on its own. + "incoming": { "dropsByReason": incoming_drops }, }, }); artifacts.push(Artifact { @@ -1997,6 +2009,17 @@ fn push_stanza( Ok(count) } +/// What the notification pass reports back: the counters the floor guard watches plus +/// the constraints it saw and could not resolve. +struct NotifCounts { + types: usize, + typed_content: usize, + stanza_tags: usize, + actions: usize, + action_shapes: usize, + drops: std::collections::BTreeMap, +} + /// Emit the incoming content-stanza read-shape catalog (`incoming/index.json`) — the /// field trees WA Web parses out of received message/receipt/call/ack stanzas. Neutral /// IR only; the codegen is a later phase. @@ -2005,17 +2028,18 @@ fn push_incoming( wa_version: &str, source: &str, module_defs: &[wa_transform::ModuleDefinition], -) -> Result { +) -> Result<(usize, std::collections::BTreeMap)> { + let (incoming, drops) = wa_scan::scan_incoming_with_diagnostics(source, module_defs); let ir = wa_ir::IncomingIr { wa_version: wa_version.to_string(), - incoming: wa_scan::scan_incoming_from_modules(source, module_defs), + incoming, }; let count = ir.incoming.len(); artifacts.push(Artifact { rel_path: PathBuf::from("incoming/index.json"), content: serde_json::to_string_pretty(&wa_ir::IrEnvelope::new(&ir))? + "\n", }); - Ok(count) + Ok((count, drops)) } /// Emit the server-initiated request read-shape catalog (`srvreq/index.json`) — the @@ -2277,8 +2301,9 @@ fn push_notif( wa_version: &str, source: &str, module_defs: &[wa_transform::ModuleDefinition], -) -> Result<(usize, usize, usize, usize, usize)> { - let ir = wa_notif::extract_notif_from_modules(source, module_defs, wa_version); +) -> Result { + let (ir, notif_drops) = + wa_notif::extract_notif_with_diagnostics(source, module_defs, wa_version); let count = ir.notifications.len(); let stanza_tags = ir.stanza_tags.len(); let typed = ir @@ -2322,7 +2347,14 @@ fn push_notif( rel_path: PathBuf::from("notif/notif.rs"), content: wa_codegen::generate_notif(&ir), }); - Ok((count, typed, stanza_tags, actions, action_shapes)) + Ok(NotifCounts { + types: count, + typed_content: typed, + stanza_tags, + actions, + action_shapes, + drops: notif_drops, + }) } fn push_wam( diff --git a/generated/manifest.json b/generated/manifest.json index f80e581..f822cb0 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -2,6 +2,11 @@ "abPropsConfigs": 2483, "appstateActions": 67, "diagnostics": { + "incoming": { + "dropsByReason": { + "response enum argument not structurally resolvable": 4 + } + }, "iq": { "candidateModules": 207, "constraints": { @@ -32,6 +37,7 @@ "actionShapes": 181, "actions": 47, "degraded": 5, + "dropsByReason": {}, "stanzaTags": 13, "typedContent": 22, "types": 27 From f7458c10c2dda1430e0f06b7c0aecfa8a0476de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 07:13:34 -0300 Subject: [PATCH 21/37] codegen: stop dropping the content fields the scanners recover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 17 — five findings. The first invalidates something I reported as good news. `wa-codegen`'s `is_attr_field` still admitted only `contentString`/`contentBytes`/ `contentInt`, so the live `contentUint` fields the previous rounds recovered were read out of the bundle, carried in the IR, and then dropped on the floor. That is why the reference Rust came out byte-identical: not "nothing broke", but "the new data is being ignored". The fourth site of the enumerate-vs-derive bug in this change, and the only one whose symptom was an *unchanged* artifact. Routing them through was not enough on its own. Every content leaf is named `content`, and `child_content_type` — which exists precisely to name a content child after its tag — handled only string and bytes, so ``, `` and `` collapsed onto one generated field with two of the three values silently lost. `ContentType` gains `Integer` and they come out as `registration: u64`, `r#type: u64`, `id: u64`. `diagnostics.incoming.dropsByReason` counted losses from parsers that never enter the artifact: this pass sees every legacy parser in the bundle, most belonging to the IQ or notification domains, and resolved before the tag gate. It reported 4; the honest number is 0. A diagnostic that does not describe the artifact beside it is worse than none. The IQ side had the mirror problem — `into_drops()` returns only the enum set, so the legacy byte pins and length bands the linker now collects were discarded before reaching `dropsByReason`. `static_byte_literal` trusted any call named `.of` and any `new` with an array argument, so `factory.of(1, 2)` would invent `literalValue: "0102"`. Both now require `Uint8Array`. A nested case exits through its own fall-through suffix: `case "a": setup(); default: return X` leaves the switch on every path, but requiring each case body to exit alone called it non-terminating and the enclosing arm borrowed the next outer case's action. 531 tests, 12/12 schemas, determinism check clean. The schema delta is one enum variant. --- crates/wa-codegen/src/emit.rs | 60 ++++++++++++++++----------- crates/wa-codegen/src/fields.rs | 36 ++++++++++------ crates/wa-codegen/src/lib.rs | 55 ++++++++++++++++++++++++ crates/wa-ir/src/iq.rs | 7 ++++ crates/wa-notif/src/actions.rs | 28 +++++++++---- crates/wa-notif/src/tests.rs | 30 +++++++++++++- crates/wa-scan/src/incoming.rs | 7 +++- crates/wa-scan/src/lib.rs | 10 +++-- crates/wa-scan/src/response.rs | 31 ++++++++++++-- crates/wa-scan/src/response_smax.rs | 15 ++++++- generated/manifest.json | 4 +- generated/schema/incoming.schema.json | 19 ++++++--- generated/schema/iq.schema.json | 19 ++++++--- generated/schema/notif.schema.json | 19 ++++++--- generated/schema/srvreq.schema.json | 19 ++++++--- generated/schema/stanza.schema.json | 19 ++++++--- 16 files changed, 295 insertions(+), 83 deletions(-) diff --git a/crates/wa-codegen/src/emit.rs b/crates/wa-codegen/src/emit.rs index f6444e4..b359ea1 100644 --- a/crates/wa-codegen/src/emit.rs +++ b/crates/wa-codegen/src/emit.rs @@ -38,20 +38,21 @@ pub(crate) fn emit_field_parse(f: &ParsedField, node_var: &str, indent: &str) -> let fmsg = rust_lit_inner(wire); let method = f.method.as_str(); - if method == wap::CONTENT_BYTES { - return vec![format!( - "{indent}let {name} = {node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default();" - )]; - } - if method == wap::CONTENT_STRING { - return vec![format!( - "{indent}let {name} = {node_var}.content_str().unwrap_or_default().to_string();" - )]; - } - if method == wap::CONTENT_INT { - return vec![format!( - "{indent}let {name} = {node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default();" - )]; + // Every remaining content spelling reads the node body and is typed by the canonical + // classifier, so a newly-recognized one (`contentUint`, `contentEnum`, + // `contentBytesRange`, `contentLiteralBytes`) is parsed rather than silently skipped. + if wap::is_content_method(method) { + return match wap::method_field_type(method) { + ParsedFieldType::Bytes => vec![format!( + "{indent}let {name} = {node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default();" + )], + ParsedFieldType::Integer => vec![format!( + "{indent}let {name} = {node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default();" + )], + _ => vec![format!( + "{indent}let {name} = {node_var}.content_str().unwrap_or_default().to_string();" + )], + }; } if !wap::is_attr_method(method) { return Vec::new(); @@ -269,16 +270,29 @@ fn emit_struct_reads( indent, ); let lit = rust_lit(tag); - let bytes = ct == wa_ir::ContentType::Bytes; + // Two spellings per kind: one that yields an `Option` for the `maybeChild` + // chain, one that unwraps for the required branch. Kept as literal text per + // kind so adding the integer reading leaves the existing bytes/string output + // byte-for-byte unchanged. + let (opt_read, req_read) = match ct { + wa_ir::ContentType::Bytes => ( + "content_bytes().map(|b| b.to_vec())", + "content_bytes().map(|b| b.to_vec()).unwrap_or_default()", + ), + wa_ir::ContentType::Integer => ( + "content_str().and_then(|s| s.parse().ok())", + "content_str().and_then(|s| s.parse().ok()).unwrap_or_default()", + ), + _ => ( + "content_str().map(|s| s.to_string())", + "content_str().unwrap_or_default().to_string()", + ), + }; if f.method == "maybeChild" { lines.push(format!( "{indent}let {id} = {base}.get_optional_child({lit})" )); - lines.push(if bytes { - format!("{indent} .and_then(|n| n.content_bytes().map(|b| b.to_vec()));") - } else { - format!("{indent} .and_then(|n| n.content_str().map(|s| s.to_string()));") - }); + lines.push(format!("{indent} .and_then(|n| n.{opt_read});")); } else { lines.push(format!( "{indent}let {id}_node = {base}.get_optional_child({lit})" @@ -287,11 +301,7 @@ fn emit_struct_reads( "{indent} .ok_or_else(|| anyhow::anyhow!(\"missing <{}>\"))?;", rust_lit_inner(tag) )); - lines.push(if bytes { - format!("{indent}let {id} = {id}_node.content_bytes().map(|b| b.to_vec()).unwrap_or_default();") - } else { - format!("{indent}let {id} = {id}_node.content_str().unwrap_or_default().to_string();") - }); + lines.push(format!("{indent}let {id} = {id}_node.{req_read};")); } inits.push(format!("{indent} {id},")); continue; diff --git a/crates/wa-codegen/src/fields.rs b/crates/wa-codegen/src/fields.rs index a1a0b3e..1aaa3dc 100644 --- a/crates/wa-codegen/src/fields.rs +++ b/crates/wa-codegen/src/fields.rs @@ -123,14 +123,17 @@ pub(crate) fn is_child_field(f: &ParsedField) -> bool { } /// A field the codegen materializes as a top-level attr-style field: an attribute -/// accessor (via [`wap::is_attr_method`]), a `contentBytes` leaf, or a `hasAttr` -/// presence marker (the latter filtered out by callers before emission). +/// accessor (via [`wap::is_attr_method`]), **any** content leaf (via +/// [`wap::is_content_method`]), or a `hasAttr` presence marker (the latter filtered out +/// by callers before emission). +/// +/// The content half is derived, not enumerated. Naming three spellings here was the +/// fourth site of that mistake in this change, and the most misleading: the scanners +/// recovered live `contentUint` fields, the IR carried them, and the reference codegen +/// dropped them on the floor — so the generated Rust came out byte-identical and the +/// unchanged output read as "nothing broke" rather than "the new data is being ignored". pub(crate) fn is_attr_field(f: &ParsedField) -> bool { - wap::is_attr_method(&f.method) - || f.method == wap::CONTENT_BYTES - || f.method == wap::CONTENT_STRING - || f.method == wap::CONTENT_INT - || f.method == wap::HAS_ATTR + wap::is_attr_method(&f.method) || wap::is_content_method(&f.method) || f.method == wap::HAS_ATTR } /// If `f` is a `child`/`maybeChild` whose body is just a content accessor @@ -145,15 +148,24 @@ pub(crate) fn child_content_type(f: &ParsedField) -> Option { if kids.is_empty() || !kids.iter().all(is_content_method) { return None; } + // Bytes wins a mixed group (the widest reading); otherwise the kids' own decoded + // type decides. Collapsing everything that was not bytes onto `String` is what made + // ``, `` and `` — three `contentUint` siblings — share a + // single generated `content` field, two of the three silently dropped. Some(if kids.iter().any(|c| c.method == wap::CONTENT_BYTES) { ContentType::Bytes + } else if kids + .iter() + .all(|c| wap::method_field_type(&c.method) == wa_ir::ParsedFieldType::Integer) + { + ContentType::Integer } else { ContentType::String }) } fn is_content_method(f: &ParsedField) -> bool { - f.method == wap::CONTENT_STRING || f.method == wap::CONTENT_BYTES + wap::is_content_method(&f.method) } fn children_of(f: &ParsedField) -> &[ParsedField] { @@ -253,10 +265,10 @@ pub(crate) fn collect_response_fields( // `child("x").contentString()` → a single `x: String` field (named by // the child tag), not a stray `content` attr that collapses siblings. if let Some(ct) = f.content_type.or_else(|| child_content_type(f)) { - let base = if ct == ContentType::Bytes { - "Vec" - } else { - "String" + let base = match ct { + ContentType::Bytes => "Vec", + ContentType::Integer => "u64", + _ => "String", }; let wrapped = if f.method == "maybeChild" { format!("Option<{base}>") diff --git a/crates/wa-codegen/src/lib.rs b/crates/wa-codegen/src/lib.rs index d1a1b07..acbb819 100644 --- a/crates/wa-codegen/src/lib.rs +++ b/crates/wa-codegen/src/lib.rs @@ -318,6 +318,61 @@ mod tests { assert!(c.contains("let body = body_node.content_str().unwrap_or_default().to_string();")); } + #[test] + fn integer_content_children_get_their_own_named_fields() { + // Two things were wrong at once, and the second hid the first: the codegen's + // content predicate admitted only three spellings, so live `contentUint` fields + // were dropped and the generated Rust came out unchanged — which read as "nothing + // broke". Once they arrived, every one of them was named `content` and collapsed + // into a single field, so two of three values were still lost. + let uint_child = |tag: &str| { + let mut c = parsed("child", tag, ParsedFieldType::String); + c.tag = Some(tag.into()); + c.children = Some(vec![parsed( + "contentUint", + "content", + ParsedFieldType::Integer, + )]); + c + }; + let ir = IqIr { + wa_version: "0.0.0".into(), + stanzas: vec![IqStanzaDef { + module_name: "WAWebDigest".into(), + namespace: "encrypt".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + parser_name: "p".into(), + exported_function: Some("digest".into()), + all_exports: vec!["digest".into()], + request: IqRequestDef { + namespace: "encrypt".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + children: vec![], + }, + response: ParsedResponse { + parser_name: "p".into(), + assertions: vec![], + fields: vec![uint_child("registration"), uint_child("id")], + ..Default::default() + }, + }], + unparseable: vec![], + }; + let c = &generate_iq(&ir); + assert!(c.contains("pub registration: u64,"), "registration field"); + assert!(c.contains("pub id: u64,"), "id field"); + assert!( + !c.contains("pub content: u64,"), + "must not collapse both onto `content`" + ); + assert!( + c.contains("content_str().and_then(|s| s.parse().ok())"), + "and the value is actually parsed" + ); + } + #[test] fn generates_spec_with_request_and_response() { let ir = IqIr { diff --git a/crates/wa-ir/src/iq.rs b/crates/wa-ir/src/iq.rs index 193aad2..0deb266 100644 --- a/crates/wa-ir/src/iq.rs +++ b/crates/wa-ir/src/iq.rs @@ -379,6 +379,13 @@ impl ParsedFieldType { pub enum ContentType { String, Bytes, + /// A decimal integer in the element body (`contentUint`, `contentInt`). Distinct + /// from [`String`] because a consumer that reads it as text has to re-parse, and + /// because collapsing it onto `String` is how three sibling integer contents ended + /// up sharing one generated field. + /// + /// [`String`]: ContentType::String + Integer, Nodes, } diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 556efc6..d57444d 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -505,7 +505,13 @@ fn terminates(stmts: &[Statement]) -> bool { /// outer case, yet the later `return` made the whole list look terminating. A statement /// that can escape upward poisons the conclusion before any later statement is consulted. fn list_exits(stmts: &[Statement], nested: bool) -> bool { - for s in stmts { + list_exits_refs(&stmts.iter().collect::>(), nested) +} + +/// [`list_exits`] over borrowed statements, so a fall-through suffix can be assembled +/// across cases without cloning the AST. +fn list_exits_refs(stmts: &[&Statement], nested: bool) -> bool { + for s in stmts.iter().copied() { if can_escape(s, nested) { return false; } @@ -550,14 +556,22 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { .alternate .as_ref() .is_some_and(|alt| stmt_exits(&i.consequent, nested) && stmt_exits(alt, nested)), - // Exhaustive only with a `default`; an empty case body falls into the next one, - // so it does not have to exit on its own. + // Exhaustive only with a `default`, and only if every case leaves the switch — + // which each case may do through its own fall-through suffix, not just its own + // body. `case X: setup(); default: return A` exits on both paths: `X` runs + // `setup()` and falls into the default's `return`. Requiring the body alone to + // exit (accepting only an EMPTY fall-through label) called that switch + // non-terminating, and the enclosing case then borrowed the next outer case's + // action for a wire tag that never produces it. Statement::SwitchStatement(sw) => { sw.cases.iter().any(|c| c.test.is_none()) - && sw - .cases - .iter() - .all(|c| c.consequent.is_empty() || list_exits(&c.consequent, true)) + && (0..sw.cases.len()).all(|i| { + let suffix: Vec<&Statement> = sw.cases[i..] + .iter() + .flat_map(|c| c.consequent.iter()) + .collect(); + list_exits_refs(&suffix, true) + }) } _ => false, } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index a4451aa..aa828a8 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1266,6 +1266,13 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr if (t.hasChild("alt")) j=t.attrString("lid"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:j}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: + switch (t.attrString("k")) { + case "a": t.attrString("setup"); + default: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, s:t.attrString("s")}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.AFTER: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, a1:t.attrString("a1")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SEQ: { var s1, s2; s1=t.attrString("jid"), s2=t.attrInt("n"); @@ -1434,3 +1441,24 @@ fn a_comma_sequence_of_assignments_binds_every_operand() { assert_eq!(wire("id"), Some("jid")); assert_eq!(wire("n"), Some("n")); } + +#[test] +fn a_nested_case_exits_through_its_own_fall_through_suffix() { + // `case "a": setup(); default: return X` — the `a` path runs `setup()` and falls into + // the default's return, so the switch DOES leave the enclosing case on every path. + // Requiring each nested case body to exit on its own (accepting only an empty + // fall-through label) called it non-terminating, and `suffix` then borrowed the + // following outer case's action for a wire tag that never produces it. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "suffix") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert!(got.contains(&"first"), "its own action survives: {got:?}"); + assert!( + !got.contains(&"third"), + "and it must not borrow the next case's: {got:?}" + ); +} diff --git a/crates/wa-scan/src/incoming.rs b/crates/wa-scan/src/incoming.rs index 56a1d35..a457284 100644 --- a/crates/wa-scan/src/incoming.rs +++ b/crates/wa-scan/src/incoming.rs @@ -69,7 +69,6 @@ pub fn scan_incoming_with_diagnostics( continue; } for mut shape in parse_module_wap_parsers(slice) { - enums.resolve(&format!("{}::{}", m.name, shape.parser_name), &mut shape); // The parser's `assertTag("…")` is the received stanza's tag. // `assertTag("receipt")` stores the tag in the assertion's `name`. let tag = shape @@ -78,7 +77,13 @@ pub fn scan_incoming_with_diagnostics( .find(|a| a.kind == AssertionKind::Tag) .and_then(|a| a.name.as_deref()) .and_then(incoming_tag); + // The tag gate comes FIRST. This pass sees every legacy parser in the bundle, + // most of which belong to the IQ or notification domains; resolving before + // the gate charged their losses to `diagnostics.incoming`, which then + // described parsers absent from `incoming/index.json`. A diagnostic that does + // not describe the artifact beside it is worse than none. if let Some(tag) = tag { + enums.resolve(&format!("{}::{}", m.name, shape.parser_name), &mut shape); out.push(IncomingDef { tag, module: m.name.clone(), diff --git a/crates/wa-scan/src/lib.rs b/crates/wa-scan/src/lib.rs index c4c8abc..6874613 100644 --- a/crates/wa-scan/src/lib.rs +++ b/crates/wa-scan/src/lib.rs @@ -276,7 +276,11 @@ pub fn scan_iq_with_diagnostics( let site = format!("{}::{}", s.module_name, s.response.parser_name); enum_linker.resolve(&site, &mut s.response); } - let enum_drops = enum_linker.into_drops(); + // Every reason the linker collected, not just the enum one: it also drains the + // legacy scanner's other unresolved constraints (a byte pin, a length band) off the + // shapes, and reading only `into_drops()` here discarded them — IQ reported no loss + // for exactly the constraints the previous round taught it to see. + let legacy_drops = enum_linker.into_drop_counts(); // Normalize ordering by an intrinsic key so the output (index.json + the // grouped codegen) is independent of bundle/source order, matching every @@ -306,8 +310,8 @@ pub fn scan_iq_with_diagnostics( // unusual response shapes that fallback exists to handle. constraint_drops: { let mut d = response_index.drop_counts(); - if !enum_drops.is_empty() { - *d.entry(response_smax::ENUM_DROP.to_string()).or_default() += enum_drops.len(); + for (reason, n) in legacy_drops { + *d.entry(reason).or_default() += n; } d }, diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index c4581a8..36fb379 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -966,11 +966,13 @@ fn merge_child_shape(fields: &mut [ParsedField], tag: &str, new_children: Vec ContentType { - if method == "contentBytes" { - ContentType::Bytes - } else { - ContentType::String + match wap::method_field_type(method) { + ParsedFieldType::Bytes => ContentType::Bytes, + ParsedFieldType::Integer => ContentType::Integer, + _ => ContentType::String, } } @@ -1598,6 +1600,27 @@ mod tests { ); } + #[test] + fn a_byte_pin_from_a_foreign_factory_is_not_trusted() { + // `factory.of(1, 2)` is an ordinary call that may return anything; reading its + // arguments as the pinned bytes would invent a `literalValue` rather than record + // an unresolved one. Same for `new Whatever([1, 2])`. + for src in [ + r#"{ e.child("t").contentLiteralBytes(factory.of(1, 2)); }"#, + r#"{ e.child("t").contentLiteralBytes(new Whatever([1, 2])); }"#, + ] { + let r = analyze_parser_ast(src, "e"); + let leaf = r.fields[0].children.as_ref().unwrap()[0].clone(); + assert_eq!(leaf.literal_value, None, "no invented pin for {src}"); + assert!( + r.unresolved + .iter() + .any(|d| d.starts_with("contentLiteralBytes@")), + "and the loss is counted for {src}" + ); + } + } + #[test] fn a_partially_literal_enum_table_is_refused_whole() { // `{A: "a", B: computed}` must not publish `["a"]` as the complete legal set — diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 00638c5..1d573be 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -44,7 +44,9 @@ use wa_ir::{ ResponseAssertion, Scalar, UnionVariant, }; -use wa_oxc::{arg_expr, as_call, as_identifier, as_int, as_string_lit, callee_method}; +use wa_oxc::{ + arg_expr, as_call, as_identifier, as_int, as_string_lit, callee_method, callee_object, +}; /// Drop reason for an enum accessor whose enum could not be resolved to its variants. pub(crate) const ENUM_DROP: &str = "response enum argument not structurally resolvable"; @@ -2050,8 +2052,12 @@ pub(crate) fn static_byte_literal(e: &Expression) -> Option> { // `Uint8Array.of(1, 2)` — the bytes are the ARGUMENTS, not an array operand. It was // documented as supported here and was not: every call expression fell through to // `None` and the pin was reported as an unresolved constraint. + // The receiver must be `Uint8Array`: `factory.of(1, 2)` is an ordinary call that may + // return anything, and reading its arguments as the pinned bytes would invent a + // `literalValue` rather than record an unresolved one. if let Some(call) = wa_oxc::as_call(e) && callee_method(call) == Some("of") + && callee_object(call).and_then(as_identifier) == Some("Uint8Array") { return call .arguments @@ -2064,7 +2070,12 @@ pub(crate) fn static_byte_literal(e: &Expression) -> Option> { Expression::NewExpression(n) => { // `new Uint8Array([...])` — the sequence is the sole argument. `new // Uint8Array(4)` (a LENGTH, not a value) deliberately does not match: it pins - // no bytes, and reading `4` as the byte 0x04 would invent a constraint. + // no bytes, and reading `4` as the byte 0x04 would invent a constraint. The + // constructor is checked for the same reason the `.of` receiver is: `new + // Whatever([1,2])` need not produce those bytes. + if as_identifier(&n.callee) != Some("Uint8Array") { + return None; + } match arg_expr(n.arguments.first()?)? { Expression::ArrayExpression(a) => a, _ => return None, diff --git a/generated/manifest.json b/generated/manifest.json index f822cb0..252e035 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -3,9 +3,7 @@ "appstateActions": 67, "diagnostics": { "incoming": { - "dropsByReason": { - "response enum argument not structurally resolvable": 4 - } + "dropsByReason": {} }, "iq": { "candidateModules": 207, diff --git a/generated/schema/incoming.schema.json b/generated/schema/incoming.schema.json index 616f179..9c658b5 100644 --- a/generated/schema/incoming.schema.json +++ b/generated/schema/incoming.schema.json @@ -92,11 +92,20 @@ }, "ContentType": { "description": "How a child/leaf node's content is accessed in the response parser.", - "type": "string", - "enum": [ - "string", - "bytes", - "nodes" + "oneOf": [ + { + "type": "string", + "enum": [ + "string", + "bytes", + "nodes" + ] + }, + { + "description": "A decimal integer in the element body (`contentUint`, `contentInt`). Distinct\nfrom [`String`] because a consumer that reads it as text has to re-parse, and\nbecause collapsing it onto `String` is how three sibling integer contents ended\nup sharing one generated field.\n\n[`String`]: ContentType::String", + "type": "string", + "const": "integer" + } ] }, "ErrorArm": { diff --git a/generated/schema/iq.schema.json b/generated/schema/iq.schema.json index 6eb4ce3..e63ed03 100644 --- a/generated/schema/iq.schema.json +++ b/generated/schema/iq.schema.json @@ -97,11 +97,20 @@ }, "ContentType": { "description": "How a child/leaf node's content is accessed in the response parser.", - "type": "string", - "enum": [ - "string", - "bytes", - "nodes" + "oneOf": [ + { + "type": "string", + "enum": [ + "string", + "bytes", + "nodes" + ] + }, + { + "description": "A decimal integer in the element body (`contentUint`, `contentInt`). Distinct\nfrom [`String`] because a consumer that reads it as text has to re-parse, and\nbecause collapsing it onto `String` is how three sibling integer contents ended\nup sharing one generated field.\n\n[`String`]: ContentType::String", + "type": "string", + "const": "integer" + } ] }, "ErrorArm": { diff --git a/generated/schema/notif.schema.json b/generated/schema/notif.schema.json index 2c8a3dd..e13a2d5 100644 --- a/generated/schema/notif.schema.json +++ b/generated/schema/notif.schema.json @@ -108,11 +108,20 @@ }, "ContentType": { "description": "How a child/leaf node's content is accessed in the response parser.", - "type": "string", - "enum": [ - "string", - "bytes", - "nodes" + "oneOf": [ + { + "type": "string", + "enum": [ + "string", + "bytes", + "nodes" + ] + }, + { + "description": "A decimal integer in the element body (`contentUint`, `contentInt`). Distinct\nfrom [`String`] because a consumer that reads it as text has to re-parse, and\nbecause collapsing it onto `String` is how three sibling integer contents ended\nup sharing one generated field.\n\n[`String`]: ContentType::String", + "type": "string", + "const": "integer" + } ] }, "ErrorArm": { diff --git a/generated/schema/srvreq.schema.json b/generated/schema/srvreq.schema.json index 58fccf7..d15b204 100644 --- a/generated/schema/srvreq.schema.json +++ b/generated/schema/srvreq.schema.json @@ -92,11 +92,20 @@ }, "ContentType": { "description": "How a child/leaf node's content is accessed in the response parser.", - "type": "string", - "enum": [ - "string", - "bytes", - "nodes" + "oneOf": [ + { + "type": "string", + "enum": [ + "string", + "bytes", + "nodes" + ] + }, + { + "description": "A decimal integer in the element body (`contentUint`, `contentInt`). Distinct\nfrom [`String`] because a consumer that reads it as text has to re-parse, and\nbecause collapsing it onto `String` is how three sibling integer contents ended\nup sharing one generated field.\n\n[`String`]: ContentType::String", + "type": "string", + "const": "integer" + } ] }, "ErrorArm": { diff --git a/generated/schema/stanza.schema.json b/generated/schema/stanza.schema.json index 7e93588..c0d0fc9 100644 --- a/generated/schema/stanza.schema.json +++ b/generated/schema/stanza.schema.json @@ -91,11 +91,20 @@ }, "ContentType": { "description": "How a child/leaf node's content is accessed in the response parser.", - "type": "string", - "enum": [ - "string", - "bytes", - "nodes" + "oneOf": [ + { + "type": "string", + "enum": [ + "string", + "bytes", + "nodes" + ] + }, + { + "description": "A decimal integer in the element body (`contentUint`, `contentInt`). Distinct\nfrom [`String`] because a consumer that reads it as text has to re-parse, and\nbecause collapsing it onto `String` is how three sibling integer contents ended\nup sharing one generated field.\n\n[`String`]: ContentType::String", + "type": "string", + "const": "integer" + } ] }, "Direction": { From 891c9a34af76992f7367ae95a0b706c0fbcbce1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 07:26:43 -0300 Subject: [PATCH 22/37] codegen: ask the classifier for the bytes branch too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 18 — one finding, and it is the same mistake inside the expression the previous commit wrote: `child_content_type` asked `method_field_type` for the integer branch and tested the exact name `contentBytes` for the bytes branch. A `child("blob").contentBytesRange(1, 128)` therefore typed as `String` and was read with `content_str()`, turning a byte payload into lossy text. Sweeping for the same shape found a fifth site: `union::field_expr` still enumerated the same three names, so a `contentUint` inside a union variant fell through to the attribute path and was read as an attribute that does not exist. Both derive from `wap::is_content_method` + `method_field_type` now. Neither is live in the pinned bundle — `generated/` does not move — so a test carries each, and the ranged-bytes one was confirmed to fail without the fix. 532 tests, determinism check clean. --- crates/wa-codegen/src/fields.rs | 27 ++++++++++++------- crates/wa-codegen/src/lib.rs | 46 +++++++++++++++++++++++++++++++++ crates/wa-codegen/src/union.rs | 23 ++++++++++------- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/crates/wa-codegen/src/fields.rs b/crates/wa-codegen/src/fields.rs index 1aaa3dc..d4a9c7e 100644 --- a/crates/wa-codegen/src/fields.rs +++ b/crates/wa-codegen/src/fields.rs @@ -152,16 +152,23 @@ pub(crate) fn child_content_type(f: &ParsedField) -> Option { // type decides. Collapsing everything that was not bytes onto `String` is what made // ``, `` and `` — three `contentUint` siblings — share a // single generated `content` field, two of the three silently dropped. - Some(if kids.iter().any(|c| c.method == wap::CONTENT_BYTES) { - ContentType::Bytes - } else if kids - .iter() - .all(|c| wap::method_field_type(&c.method) == wa_ir::ParsedFieldType::Integer) - { - ContentType::Integer - } else { - ContentType::String - }) + // + // Both branches ask the classifier. Using it for integers and an exact `contentBytes` + // name test for bytes — in the same expression — would have typed a + // `contentBytesRange` child as `String` and read it with `content_str()`. + let decodes_to = |c: &ParsedField| wap::method_field_type(&c.method); + Some( + if kids.iter().any(|c| decodes_to(c) == ParsedFieldType::Bytes) { + ContentType::Bytes + } else if kids + .iter() + .all(|c| decodes_to(c) == ParsedFieldType::Integer) + { + ContentType::Integer + } else { + ContentType::String + }, + ) } fn is_content_method(f: &ParsedField) -> bool { diff --git a/crates/wa-codegen/src/lib.rs b/crates/wa-codegen/src/lib.rs index acbb819..4aab9d3 100644 --- a/crates/wa-codegen/src/lib.rs +++ b/crates/wa-codegen/src/lib.rs @@ -373,6 +373,52 @@ mod tests { ); } + #[test] + fn a_ranged_byte_content_child_is_still_bytes() { + // `child("blob").contentBytesRange(1, 128)` decodes to bytes. Asking the + // classifier for the integer branch while testing the exact name `contentBytes` + // for the bytes branch — in one expression — typed this as `String` and read it + // with `content_str()`, so a byte payload came out as lossy text. + let mut blob = parsed("child", "blob", ParsedFieldType::String); + blob.tag = Some("blob".into()); + blob.children = Some(vec![parsed( + "contentBytesRange", + "content", + ParsedFieldType::Bytes, + )]); + let ir = IqIr { + wa_version: "0.0.0".into(), + stanzas: vec![IqStanzaDef { + module_name: "WAWebBlob".into(), + namespace: "encrypt".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + parser_name: "p".into(), + exported_function: Some("blob".into()), + all_exports: vec!["blob".into()], + request: IqRequestDef { + namespace: "encrypt".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + children: vec![], + }, + response: ParsedResponse { + parser_name: "p".into(), + assertions: vec![], + fields: vec![blob], + ..Default::default() + }, + }], + unparseable: vec![], + }; + let c = &generate_iq(&ir); + assert!(c.contains("pub blob: Vec,"), "typed as bytes:\n{c}"); + assert!( + c.contains("blob_node.content_bytes()"), + "and read as bytes:\n{c}" + ); + } + #[test] fn generates_spec_with_request_and_response() { let ir = IqIr { diff --git a/crates/wa-codegen/src/union.rs b/crates/wa-codegen/src/union.rs index 83acd2d..67f5fad 100644 --- a/crates/wa-codegen/src/union.rs +++ b/crates/wa-codegen/src/union.rs @@ -704,16 +704,19 @@ fn value_payload(enum_name: &str, variant: &str, fields: &[ParsedField], node_va /// (the variant guard already proved the required attrs present). fn field_expr(f: &ParsedField, node_var: &str) -> String { let method = f.method.as_str(); - if method == wap::CONTENT_STRING { - return format!("{node_var}.content_str().unwrap_or_default().to_string()"); - } - if method == wap::CONTENT_BYTES { - return format!("{node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default()"); - } - if method == wap::CONTENT_INT { - return format!( - "{node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default()" - ); + // Derived, not enumerated — the same three names were spelled out here as in + // `is_attr_field` and `child_content_type`, so a `contentUint` inside a union variant + // fell through to the attribute path and was read as an attribute that does not exist. + if wap::is_content_method(method) { + return match wap::method_field_type(method) { + ParsedFieldType::Bytes => { + format!("{node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default()") + } + ParsedFieldType::Integer => { + format!("{node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default()") + } + _ => format!("{node_var}.content_str().unwrap_or_default().to_string()"), + }; } let wire = f.wire_name.as_deref().unwrap_or(&f.name); let flit = rust_lit(wire); From 7219c32c24c82c1bbedfe1f02e3f241dc9b47650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 07:29:09 -0300 Subject: [PATCH 23/37] notif: read the error paths of a try, and let a try terminate a case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `collect_returns` walked only a `try` body. WA wraps a parse in `try { … } catch (e) { … }` and the handler's return is as legal a shape as the body's, so the error path was omitted entirely; a returning `finally` was invisible too. Both are now collected, with the pre-`try` scope (nothing the body wrote is guaranteed to have run when the handler is entered), and a finalizer that returns discards what `try`/`catch` produced — it is the only shape the statement can yield. Writing the test for that exposed a second gap it depends on: `stmt_exits` had no arm for `try`, so `try { return X } finally { return Y }` — which always returns — read as fall-through and the arm borrowed the next case's action. A `try` exits when its finalizer does, or when the body and (if present) the handler both do. This finding sat unread since round 16: my thread sweep used `reviewThreads(first: 100)` and the PR has 107, so the newest ones were silently truncated — including five I had already answered but never actually resolved. Sweeps paginate now. 534 tests, determinism check clean. --- crates/wa-notif/src/actions.rs | 46 ++++++++++++++++++++++++++++++++-- crates/wa-notif/src/tests.rs | 38 +++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index d57444d..14d98fd 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -563,6 +563,21 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { // exit (accepting only an EMPTY fall-through label) called that switch // non-terminating, and the enclosing case then borrowed the next outer case's // action for a wire tag that never produces it. + // A `finally` that exits settles it on its own; otherwise the `try` body must + // exit and, if there is a handler, so must it. `try { return X } finally + // { return Y }` always returns, and treating it as fall-through let the arm + // borrow the next case's action. + Statement::TryStatement(t) => { + let finalizer_exits = t + .finalizer + .as_ref() + .is_some_and(|f| list_exits(&f.body, nested)); + finalizer_exits + || (list_exits(&t.block.body, nested) + && t.handler + .as_ref() + .is_none_or(|h| list_exits(&h.body.body, nested))) + } Statement::SwitchStatement(sw) => { sw.cases.iter().any(|c| c.test.is_none()) && (0..sw.cases.len()).all(|i| { @@ -792,7 +807,26 @@ fn collect_returns<'b, 'a>( tombstone_branch_writes(s, &mut scope); } Statement::TryStatement(t) => { - collect_returns(&as_refs(&t.block.body), &scope, out); + // The `try` body and its `catch` are alternative paths — WA wraps a + // parse in `try { … } catch (e) { … }` and the handler's return is as + // legal as the body's — and both see the pre-`try` scope, since nothing + // the body wrote is guaranteed to have run when the handler is entered. + let mut paths = Vec::new(); + collect_returns(&as_refs(&t.block.body), &scope, &mut paths); + if let Some(h) = &t.handler { + collect_returns(&as_refs(&h.body.body), &scope, &mut paths); + } + // A `finally` that returns discards whatever `try`/`catch` produced — + // it is the only shape the statement can yield. + let mut finalizer = Vec::new(); + if let Some(f) = &t.finalizer { + collect_returns(&as_refs(&f.body), &scope, &mut finalizer); + } + out.extend(if finalizer.is_empty() { + paths + } else { + finalizer + }); // A `try` body may have run in part; nothing it wrote is certain after it. tombstone_branch_writes(s, &mut scope); } @@ -884,7 +918,15 @@ fn assigned_names<'a>(s: &'a Statement, out: &mut Vec<&'a str>) { assigned_names(a, out); } } - Statement::TryStatement(t) => t.block.body.iter().for_each(|s| assigned_names(s, out)), + Statement::TryStatement(t) => { + t.block.body.iter().for_each(|s| assigned_names(s, out)); + if let Some(h) = &t.handler { + h.body.body.iter().for_each(|s| assigned_names(s, out)); + } + if let Some(f) = &t.finalizer { + f.body.iter().for_each(|s| assigned_names(s, out)); + } + } Statement::SwitchStatement(sw) => sw .cases .iter() diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index aa828a8..77256ff 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1266,6 +1266,12 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr if (t.hasChild("alt")) j=t.attrString("lid"); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:j}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.TRY: + try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, ok:t.attrString("ok")}; } + catch (e) { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, err:t.attrString("err")}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FIN: + try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } + finally { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, f:t.attrString("f")}; } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1462,3 +1468,33 @@ fn a_nested_case_exits_through_its_own_fall_through_suffix() { "and it must not borrow the next case's: {got:?}" ); } + +#[test] +fn a_catch_block_return_is_a_legal_action() { + // WA wraps a parse in `try { … } catch (e) { … }` and the handler's return is as + // legal as the body's. Walking only the `try` body omitted the error path entirely. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "try_tag") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert!(got.contains(&"first"), "the try body's action: {got:?}"); + assert!(got.contains(&"second"), "and the catch's: {got:?}"); +} + +#[test] +fn a_returning_finally_overrides_the_try_body() { + // A `finally` that returns discards whatever `try`/`catch` produced — it is the only + // shape the statement can yield, so reporting the body's would be a shape no + // execution reaches. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "fin") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!(got, ["third"], "only the finalizer's shape: {got:?}"); +} From 42c93c52ffd15eaed2e98aa0376ffdefd6f949f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 07:51:18 -0300 Subject: [PATCH 24/37] notif: decide the finally override by control flow, and refuse two-source fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 19 — five findings, four applied. The finalizer branch added last commit decided the override by asking whether the finalizer had yielded any shape. That is wrong at both ends: `finally { if (c) return B }` leaves A legal when `c` is false, and `finally { throw e }` yields nothing yet leaves the vector empty, so A was published although it never escapes. Only a finalizer that leaves on every path replaces the earlier shapes. `find_accessor_at`'s wrapper descent took whichever argument read the wire first, so `combine(c.attrString("a"), c.attrString("b"))` published `a` as the single source of a value that depends on both. A `NotifActionField` names one `wireName` with one requiredness, so two distinct reads are refused — the rule a key bound to two different reads across branches already follows. The same read twice is not ambiguous and still resolves. `w:gp2`'s `create.reason` validates against a module-local table, which is not a nameable `o("Mod").ENUM` reference, so it shipped as `"type": "enum"` with no values and no signal anywhere. The action path has no linker to drain a pending marker, so it reports its own losses: `diagnostics.notif.dropsByReason` now carries 1. The smax enum-drop occurrence key was `site:attr`, which collapsed one parser reading the same attribute off two descended nodes into a single loss. It includes the source path now (no change in today's count — preventive). Not applied: enforcing `literalValue` / `byteLength` / `byteMin`-`byteMax` in the generated parsers. Real gap, but not a byte-content one — the reference codegen decodes and does not validate ANY constraint: 2178 `literalValue`, 169 `intMin`, 88 `byteLength`, 281 `enumRef` all go unchecked today. Enforcing only the newest kind would be arbitrary; the whole enforcement layer is its own change. Answered on the thread. 537 tests, 12/12 schemas, determinism check clean. --- crates/wa-notif/src/actions.rs | 101 +++++++++++++++++++++++----- crates/wa-notif/src/lib.rs | 12 +++- crates/wa-notif/src/tests.rs | 58 +++++++++++++++- crates/wa-scan/src/response_smax.rs | 18 +++-- generated/manifest.json | 4 +- 5 files changed, 167 insertions(+), 26 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 14d98fd..f4ae44b 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -50,6 +50,15 @@ pub(crate) struct ConstResolver<'a> { /// per enum-accessor field — including a second time by the bare-value fallback, which /// re-walks the same shapes. enums: std::cell::RefCell>>, + /// Enum-valued action fields whose allowed-value table could not be named, keyed + /// `wireName@table` so two fields losing the same constraint count twice and one + /// field seen twice counts once. + /// + /// The action path has no linker to drain a pending marker, so it reports here. The + /// `w:gp2` `create.reason` field shipped as `"type": "enum"` with no values and no + /// signal anywhere — the exact "lost or absent?" ambiguity the rest of the change + /// exists to remove. + enum_drops: std::cell::RefCell>, } impl<'a> ConstResolver<'a> { @@ -58,6 +67,7 @@ impl<'a> ConstResolver<'a> { slices, cache: std::cell::RefCell::new(HashMap::new()), enums: std::cell::RefCell::new(HashMap::new()), + enum_drops: Default::default(), } } @@ -92,6 +102,33 @@ impl<'a> ConstResolver<'a> { resolved } + /// The enum an action field validates against, reporting the loss when there is a + /// table but it is not a nameable `o("Mod").ENUM` reference (a module-local object, + /// a computed set). `wire_name` identifies the occurrence in the report. + fn action_enum_ref( + &self, + method: &str, + arg: Option<&Expression>, + wire_name: &str, + ) -> Option { + if wap::method_field_type(method) != wa_ir::ParsedFieldType::Enum { + return None; + } + let resolved = arg.and_then(|a| self.enum_ref(a)); + if resolved.is_none() { + self.enum_drops + .borrow_mut() + .insert(format!("{wire_name}@{method}")); + } + resolved + } + + /// The allowed-value tables seen on action fields and not recoverable, for + /// `diagnostics.notif.dropsByReason`. + pub(crate) fn take_enum_drops(&self) -> usize { + self.enum_drops.borrow().len() + } + fn resolve_enum_uncached(&self, module: &str, name: &str) -> Option { let def = wa_enums::resolve_named_enum(self.slices.get(module)?, module, name)?; let variants: Vec = def @@ -816,17 +853,23 @@ fn collect_returns<'b, 'a>( if let Some(h) = &t.handler { collect_returns(&as_refs(&h.body.body), &scope, &mut paths); } - // A `finally` that returns discards whatever `try`/`catch` produced — - // it is the only shape the statement can yield. + // What the finalizer does to the earlier shapes is a control-flow + // question, not a "did it yield anything" one. Emptiness gets both ends + // wrong: `finally { if (c) return B }` keeps A legal when `c` is false, + // and `finally { throw e }` yields nothing at all yet leaves the vector + // empty. Only a finalizer that leaves on EVERY path replaces them. let mut finalizer = Vec::new(); if let Some(f) = &t.finalizer { collect_returns(&as_refs(&f.body), &scope, &mut finalizer); } - out.extend(if finalizer.is_empty() { - paths - } else { - finalizer - }); + let finalizer_settles = t + .finalizer + .as_ref() + .is_some_and(|f| list_exits(&f.body, false)); + if !finalizer_settles { + out.extend(paths); + } + out.extend(finalizer); // A `try` body may have run in part; nothing it wrote is certain after it. tombstone_branch_writes(s, &mut scope); } @@ -1410,13 +1453,14 @@ fn read_field<'b, 'a>( } let optional_by_guard = is_guarded(e); let acc = find_accessor(strip_guard(e), scope)?; + let enum_ref = consts.action_enum_ref(&acc.method, acc.enum_arg, &acc.wire_name); Some(NotifActionField { name: key.to_string(), wire_name: acc.wire_name, field_type: wap::method_field_type(&acc.method), required: !optional_by_guard && !wap::is_optional_method(&acc.method), content: acc.content, - enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), + enum_ref, }) } @@ -1482,13 +1526,14 @@ fn is_nullish(e: &Expression) -> bool { /// The field an accessor yields when it sits behind a presence guard: always optional, /// because the guard exists precisely so the attribute may be absent. fn guarded_field(key: &str, acc: Accessor, consts: &ConstResolver) -> NotifActionField { + let enum_ref = consts.action_enum_ref(&acc.method, acc.enum_arg, &acc.wire_name); NotifActionField { name: key.to_string(), wire_name: acc.wire_name, field_type: wap::method_field_type(&acc.method), required: false, content: acc.content, - enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), + enum_ref, } } @@ -1636,10 +1681,26 @@ fn find_accessor_at<'b, 'a>( } } // A wrapper call (`userJidToUserWid(…)`, a local normaliser): look inside. - call.arguments + // + // Exactly one argument may read the wire. `combine(c.attrString("a"), + // c.attrString("b"))` derives its value from both, and a `NotifActionField` names one + // `wireName` with one requiredness — so taking whichever came first would state as + // fact something the IR cannot express. Refused instead, the same rule a key bound to + // two different reads across branches already follows. + let mut found = call + .arguments .iter() .filter_map(arg_expr) - .find_map(|a| find_accessor_at(a, scope, depth + 1)) + .filter_map(|a| find_accessor_at(a, scope, depth + 1)); + let first = found.next()?; + match found.next() { + // A repeated read of the SAME attribute is not ambiguous — a normaliser given the + // same value twice still has one source. + Some(second) if second.wire_name != first.wire_name || second.method != first.method => { + None + } + _ => Some(first), + } } /// The enum table an enum-valued accessor validates against. @@ -1720,13 +1781,17 @@ fn collect_accessor_fields<'b, 'a>( for (shape, inner) in fn_result_shapes(e, &scope) { bare += 1; let fields = find_accessor(shape, &inner) - .map(|acc| NotifActionField { - name: acc.wire_name.clone(), - wire_name: acc.wire_name, - field_type: wap::method_field_type(&acc.method), - required: !wap::is_optional_method(&acc.method), - content: acc.content, - enum_ref: acc.enum_arg.and_then(|a| consts.enum_ref(a)), + .map(|acc| { + let enum_ref = + consts.action_enum_ref(&acc.method, acc.enum_arg, &acc.wire_name); + NotifActionField { + name: acc.wire_name.clone(), + wire_name: acc.wire_name, + field_type: wap::method_field_type(&acc.method), + required: !wap::is_optional_method(&acc.method), + content: acc.content, + enum_ref, + } }) .into_iter() .collect(); diff --git a/crates/wa-notif/src/lib.rs b/crates/wa-notif/src/lib.rs index a2a8c8c..55f4b54 100644 --- a/crates/wa-notif/src/lib.rs +++ b/crates/wa-notif/src/lib.rs @@ -203,7 +203,17 @@ pub fn extract_notif_with_diagnostics( } } - let drops = enums.into_drop_counts(); + let mut drops = enums.into_drop_counts(); + // The action path has no linker to drain a pending marker, so it reports its own + // unresolvable allowed-value tables here. Without this the `w:gp2` `create.reason` + // field shipped as `"type": "enum"` with no values and nothing anywhere saying a + // constraint had been lost. + let action_enum_drops = consts.take_enum_drops(); + if action_enum_drops > 0 { + *drops + .entry("action enum table not structurally resolvable".to_string()) + .or_default() += action_enum_drops; + } ( NotifIr { wa_version: wa_version.to_string(), diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 77256ff..16551a2 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1272,6 +1272,14 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FIN: try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } finally { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, f:t.attrString("f")}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FINCOND: + try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } + finally { if (t.hasChild("z")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FINTHROW: + try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } + finally { throw new Error("x"); } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MULTI: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:combine(t.attrString("jid"), t.attrString("lid")), plain:t.attrString("plain")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1498,3 +1506,51 @@ fn a_returning_finally_overrides_the_try_body() { .collect(); assert_eq!(got, ["third"], "only the finalizer's shape: {got:?}"); } + +#[test] +fn a_conditional_finally_leaves_the_try_result_legal() { + // `try { return A } finally { if (c) return B }` returns A when `c` is false. Deciding + // the override by "did the finalizer yield any shape" reported only B. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let mut got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "fincond") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + got.sort_unstable(); + assert_eq!(got, ["first", "second"], "both paths are legal: {got:?}"); +} + +#[test] +fn a_throwing_finally_yields_no_shape_at_all() { + // The mirror case: `finally { throw }` collects no returns, so emptiness read as + // "no override" and the try body's shape was published although it never escapes. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "finthrow") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert!(got.is_empty(), "nothing is returned from this arm: {got:?}"); +} + +#[test] +fn a_wrapper_over_two_accessors_refuses_the_field() { + // `combine(t.attrString("jid"), t.attrString("lid"))` derives its value from both, and + // a field names ONE `wireName`. Taking the first stated as fact something the IR + // cannot express; the sibling read on the same arm must still survive. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "multi"); + assert!( + !a.fields.iter().any(|f| f.name == "id"), + "the ambiguous key is refused: {:?}", + a.fields + ); + assert!( + a.fields.iter().any(|f| f.name == "plain"), + "and the unambiguous sibling is kept: {:?}", + a.fields + ); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 1d573be..5adabe3 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -821,6 +821,8 @@ fn classify_call( Some((m, ft, bl)) => { let (field_type, int_range) = int_range_and_type(inner, args, ft); let (cbl, byte_range) = content_byte_length(inner, args); + let enum_ref = + enum_arg_ref(inner, args, resolver, site, source_path.as_deref()); Binding::Field { method: optional_variant(&m), field_type, @@ -831,7 +833,7 @@ fn classify_call( wire_name, source_path, literal_value: None, - enum_ref: enum_arg_ref(inner, args, resolver, site), + enum_ref, reference_path: None, } } @@ -852,6 +854,8 @@ fn classify_call( Some((m, ft, bl)) => { let (field_type, int_range) = int_range_and_type(Some(other), args, ft); let (cbl, byte_range) = content_byte_length(Some(other), args); + let enum_ref = + enum_arg_ref(Some(other), args, resolver, site, source_path.as_deref()); Binding::Field { method: m, field_type, @@ -862,7 +866,7 @@ fn classify_call( wire_name, source_path, literal_value: None, - enum_ref: enum_arg_ref(Some(other), args, resolver, site), + enum_ref, reference_path: None, } } @@ -1003,6 +1007,7 @@ fn enum_arg_ref( args: &[Argument], resolver: &Resolver, site: &str, + source_path: Option<&[String]>, ) -> Option { // Gated on the CLASSIFIER, not a local list of spellings. Whitelisting // `attrStringEnum`/`contentStringEnum` left `attrEnum`, `maybeAttrEnum`, `contentEnum` @@ -1015,10 +1020,13 @@ fn enum_arg_ref( // is an identifier / `X.value`, the attr a string, and — in the `optional(ACC, …)` // form — the leading accessor ref is itself `o("WASmaxParseUtils").attrStringEnum`, // excluded by requiring a non-`WASmaxParse*` owner module). - // The wire attribute the accessor reads, as the occurrence discriminator: two fields - // validating against the same unresolvable enum are two lost constraints. + // The occurrence discriminator is the node PATH plus the attribute name: two fields + // validating against the same unresolvable enum are two lost constraints, and one + // parser reading `state` off both `` and `` is two of them. The + // attribute alone collapsed those into one. let occurrence = format!( - "{site}:{}", + "{site}:{}:{}", + source_path.map(|p| p.join("/")).unwrap_or_default(), args.iter() .filter_map(arg_expr) .find_map(as_string_lit) diff --git a/generated/manifest.json b/generated/manifest.json index 252e035..e5d8814 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -35,7 +35,9 @@ "actionShapes": 181, "actions": 47, "degraded": 5, - "dropsByReason": {}, + "dropsByReason": { + "action enum table not structurally resolvable": 1 + }, "stanzaTags": 13, "typedContent": 22, "types": 27 From 21815bdac2f34d490938a5159a961ba4a0ee2f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 08:14:30 -0300 Subject: [PATCH 25/37] codegen: decode contentUint as big-endian bytes, not decimal text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 20 — four findings. The first means the fields the last two rounds "recovered" would have decoded to zero. `contentUint` is not a text integer. WA calls it `contentUint(3)` for a prekey id, `contentUint(4)` for a registration id, `contentUint(1)` for a type — the argument is a BYTE COUNT and the body is big-endian binary. Grouping it with `contentInt` (which really is decimal text: a follower count, a participant count) meant the generated parser ran `content_str().parse()` over binary and silently produced `0` for every one, live in `WAWebDigestKeyJob`. The byte count was being dropped on both scanner paths too, so the IR did not even record the width; `byteLength` 1/3/4 now rides on those fields. The other three: * the multi-source refusal added last commit compared only the SECOND recovered accessor, so `combine(a("jid"), a("jid"), a("lid"))` matched on the second and never saw the conflicting third. Every argument is inspected now. * the action enum-drop key was resolver-wide `wireName@method`, so two actions each losing `reason` counted once. Keyed by the table argument's source span, which is a stronger identity in both directions than the action name would be. * the request-op fallback classified a candidate by its name suffix with an empty assertion list, so a `…Success…` module that asserts `type="error"` could be handed back as an operation's only response shape. The name gate stays as a cheap pre-filter; the parsed assertions decide. 538 tests, 12/12 schemas, determinism check clean. --- crates/wa-codegen/src/emit.rs | 73 +++++++++++++++++++--------- crates/wa-codegen/src/lib.rs | 6 ++- crates/wa-codegen/src/union.rs | 10 +--- crates/wa-notif/src/actions.rs | 31 +++++++----- crates/wa-notif/src/tests.rs | 22 ++++++++- crates/wa-scan/src/response.rs | 6 ++- crates/wa-scan/src/response_index.rs | 8 +++ crates/wa-scan/src/response_smax.rs | 9 ++++ generated/incoming/index.json | 6 ++- generated/iq/index.json | 12 +++-- generated/manifest.json | 4 +- 11 files changed, 132 insertions(+), 55 deletions(-) diff --git a/crates/wa-codegen/src/emit.rs b/crates/wa-codegen/src/emit.rs index b359ea1..8075637 100644 --- a/crates/wa-codegen/src/emit.rs +++ b/crates/wa-codegen/src/emit.rs @@ -42,17 +42,10 @@ pub(crate) fn emit_field_parse(f: &ParsedField, node_var: &str, indent: &str) -> // classifier, so a newly-recognized one (`contentUint`, `contentEnum`, // `contentBytesRange`, `contentLiteralBytes`) is parsed rather than silently skipped. if wap::is_content_method(method) { - return match wap::method_field_type(method) { - ParsedFieldType::Bytes => vec![format!( - "{indent}let {name} = {node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default();" - )], - ParsedFieldType::Integer => vec![format!( - "{indent}let {name} = {node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default();" - )], - _ => vec![format!( - "{indent}let {name} = {node_var}.content_str().unwrap_or_default().to_string();" - )], - }; + return vec![format!( + "{indent}let {name} = {node_var}.{};", + content_decoder(method) + )]; } if !wap::is_attr_method(method) { return Vec::new(); @@ -86,6 +79,26 @@ pub(crate) fn emit_field_parse(f: &ParsedField, node_var: &str, indent: &str) -> } } +/// How to read a content leaf off a node, as a full expression yielding the field's type. +/// +/// `contentInt()` and `contentUint(N)` are BOTH integers and are decoded completely +/// differently: `contentInt` is decimal text (a follower count), while `contentUint(N)` +/// is N big-endian bytes (a 3-byte prekey id, a 4-byte registration id). Reading the +/// latter as text makes every one of them silently `0`, which is what grouping them by +/// `method_field_type` alone did. +pub(crate) fn content_decoder(method: &str) -> &'static str { + if method == "contentUint" { + return "content_bytes()\n .map(|b| b.iter().fold(0u64, |acc, &x| (acc << 8) | x as u64))\n .unwrap_or_default()"; + } + match wap::method_field_type(method) { + ParsedFieldType::Bytes => "content_bytes().map(|b| b.to_vec()).unwrap_or_default()", + ParsedFieldType::Integer => { + "content_str().and_then(|s| s.parse().ok()).unwrap_or_default()" + } + _ => "content_str().unwrap_or_default().to_string()", + } +} + /// Emit the body of `parse_response`, ending with `Ok( {{ ... }})`. pub(crate) fn emit_response_parser( fields: &[ParsedField], @@ -274,19 +287,33 @@ fn emit_struct_reads( // chain, one that unwraps for the required branch. Kept as literal text per // kind so adding the integer reading leaves the existing bytes/string output // byte-for-byte unchanged. - let (opt_read, req_read) = match ct { - wa_ir::ContentType::Bytes => ( - "content_bytes().map(|b| b.to_vec())", - "content_bytes().map(|b| b.to_vec()).unwrap_or_default()", - ), - wa_ir::ContentType::Integer => ( - "content_str().and_then(|s| s.parse().ok())", - "content_str().and_then(|s| s.parse().ok()).unwrap_or_default()", - ), - _ => ( - "content_str().map(|s| s.to_string())", - "content_str().unwrap_or_default().to_string()", + // + // The child's OWN accessor decides when it has one, because `ContentType` + // cannot tell decimal text from big-endian bytes: `contentInt` and + // `contentUint` are both `Integer` and decode oppositely. + let leaf_method = children_of(f) + .iter() + .map(|c| c.method.as_str()) + .find(|m| wap::is_content_method(m)); + let (opt_read, req_read) = match leaf_method { + Some("contentUint") => ( + "content_bytes().map(|b| b.iter().fold(0u64, |acc, &x| (acc << 8) | x as u64))", + "content_bytes()\n .map(|b| b.iter().fold(0u64, |acc, &x| (acc << 8) | x as u64))\n .unwrap_or_default()", ), + _ => match ct { + wa_ir::ContentType::Bytes => ( + "content_bytes().map(|b| b.to_vec())", + "content_bytes().map(|b| b.to_vec()).unwrap_or_default()", + ), + wa_ir::ContentType::Integer => ( + "content_str().and_then(|s| s.parse().ok())", + "content_str().and_then(|s| s.parse().ok()).unwrap_or_default()", + ), + _ => ( + "content_str().map(|s| s.to_string())", + "content_str().unwrap_or_default().to_string()", + ), + }, }; if f.method == "maybeChild" { lines.push(format!( diff --git a/crates/wa-codegen/src/lib.rs b/crates/wa-codegen/src/lib.rs index 4aab9d3..e7af545 100644 --- a/crates/wa-codegen/src/lib.rs +++ b/crates/wa-codegen/src/lib.rs @@ -367,9 +367,11 @@ mod tests { !c.contains("pub content: u64,"), "must not collapse both onto `content`" ); + // The accessor reads N big-endian bytes, not decimal text — a 3-byte prekey id, + // a 4-byte registration id. Parsing that as a string makes every one silently 0. assert!( - c.contains("content_str().and_then(|s| s.parse().ok())"), - "and the value is actually parsed" + c.contains("content_bytes()") && c.contains("(acc << 8) | x as u64"), + "and the value is decoded big-endian:\n{c}" ); } diff --git a/crates/wa-codegen/src/union.rs b/crates/wa-codegen/src/union.rs index 67f5fad..f17067b 100644 --- a/crates/wa-codegen/src/union.rs +++ b/crates/wa-codegen/src/union.rs @@ -708,15 +708,7 @@ fn field_expr(f: &ParsedField, node_var: &str) -> String { // `is_attr_field` and `child_content_type`, so a `contentUint` inside a union variant // fell through to the attribute path and was read as an attribute that does not exist. if wap::is_content_method(method) { - return match wap::method_field_type(method) { - ParsedFieldType::Bytes => { - format!("{node_var}.content_bytes().map(|b| b.to_vec()).unwrap_or_default()") - } - ParsedFieldType::Integer => { - format!("{node_var}.content_str().and_then(|s| s.parse().ok()).unwrap_or_default()") - } - _ => format!("{node_var}.content_str().unwrap_or_default().to_string()"), - }; + return format!("{node_var}.{}", crate::emit::content_decoder(method)); } let wire = f.wire_name.as_deref().unwrap_or(&f.name); let flit = rust_lit(wire); diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index f4ae44b..bc611ff 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -116,9 +116,19 @@ impl<'a> ConstResolver<'a> { } let resolved = arg.and_then(|a| self.enum_ref(a)); if resolved.is_none() { - self.enum_drops - .borrow_mut() - .insert(format!("{wire_name}@{method}")); + // Keyed by the table argument's SOURCE SPAN when there is one. `wireName@method` + // was resolver-wide, so two actions each losing `reason` counted once. A span is + // a stronger identity than the action name in both directions: two arms with two + // `attrEnumOrNullIfUnknown("reason", …)` calls are two losses, and two arms + // sharing one helper's call are one constraint referenced twice. + let key = arg.map_or_else( + || format!("{wire_name}@{method}"), + |a| { + let sp = oxc_span::GetSpan::span(a); + format!("{}..{}@{method}", sp.start, sp.end) + }, + ); + self.enum_drops.borrow_mut().insert(key); } resolved } @@ -1687,20 +1697,19 @@ fn find_accessor_at<'b, 'a>( // `wireName` with one requiredness — so taking whichever came first would state as // fact something the IR cannot express. Refused instead, the same rule a key bound to // two different reads across branches already follows. + // EVERY argument is inspected, not just the second: `combine(a("jid"), a("jid"), + // a("lid"))` matches on the second and conflicts on the third, and stopping early + // published `jid` again for a value that also depends on `lid`. let mut found = call .arguments .iter() .filter_map(arg_expr) .filter_map(|a| find_accessor_at(a, scope, depth + 1)); let first = found.next()?; - match found.next() { - // A repeated read of the SAME attribute is not ambiguous — a normaliser given the - // same value twice still has one source. - Some(second) if second.wire_name != first.wire_name || second.method != first.method => { - None - } - _ => Some(first), - } + // A repeated read of the SAME attribute is not ambiguous — a normaliser given the + // same value twice still has one source. + let conflicts = found.any(|a| a.wire_name != first.wire_name || a.method != first.method); + (!conflicts).then_some(first) } /// The enum table an enum-valued accessor validates against. diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 16551a2..4525024 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1280,6 +1280,8 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr finally { throw new Error("x"); } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MULTI: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:combine(t.attrString("jid"), t.attrString("lid")), plain:t.attrString("plain")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MULTI3: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:combine(t.attrString("jid"), t.attrString("jid"), t.attrString("lid")), plain:t.attrString("plain")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1554,3 +1556,21 @@ fn a_wrapper_over_two_accessors_refuses_the_field() { a.fields ); } + +#[test] +fn a_conflict_past_the_second_wrapper_argument_still_refuses() { + // `combine(a("jid"), a("jid"), a("lid"))` — the second read matches the first, so + // comparing only those two accepted `jid` and never looked at the conflicting third. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "multi3"); + assert!( + !a.fields.iter().any(|f| f.name == "id"), + "every argument is inspected: {:?}", + a.fields + ); + assert!( + a.fields.iter().any(|f| f.name == "plain"), + "and the unambiguous sibling survives: {:?}", + a.fields + ); +} diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 36fb379..8cb243b 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -314,7 +314,11 @@ fn field_from_call( // reading their arguments published unconstrained `bytes` — an emitter would think // any payload passes where the parser accepts one sequence or one length band. match method { - wap::CONTENT_BYTES => { + // `contentBytes(64)` and `contentUint(3)` both take a BYTE COUNT. `contentUint` + // is not a decimal string: WA packs a prekey id into 3 bytes and a registration + // id into 4, big-endian, so the length is part of the wire contract and dropping + // it left the field looking like unbounded text. + wap::CONTENT_BYTES | "contentUint" => { if let Some(Expression::NumericLiteral(n)) = arg0 { f.byte_length = Some(n.value as u32); } diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 6eb8ca6..d707bd8 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -86,6 +86,14 @@ impl ResponseIndex { .find(|(_, pr)| !pr.fields.is_empty()) .map(|(_, pr)| pr) { + // The name gate above is a cheap pre-filter; this is the decision. WA's + // names contradict the wire discriminator often enough that + // `SetResponsePreKeySuccessVnameFailure` reads as a success while + // asserting `type="error"` — classifying on the suffix alone would hand + // an error parser back as the operation's single response shape. + if variant_kind(variant, &pr.assertions) != ResponseVariantKind::Success { + continue; + } return Some(pr); } } diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 5adabe3..6f95d70 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -1215,6 +1215,15 @@ fn content_byte_length( accessor: Option<&str>, args: &[Argument], ) -> (Option, Option<(u32, u32)>) { + // `contentUint(N)` reads N big-endian bytes, so N is a fixed length like + // `contentBytes(N)`'s — not a bound. + if accessor == Some("contentUint") { + let n = args + .iter() + .filter_map(|a| arg_expr(a).and_then(as_int)) + .next(); + return (n.and_then(|n| u32::try_from(n).ok()), None); + } if accessor != Some("contentBytesRange") { return (None, None); } diff --git a/generated/incoming/index.json b/generated/incoming/index.json index f23a1f3..99d73e1 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -496,7 +496,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 3 } ] }, @@ -555,7 +556,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 4 } ] } diff --git a/generated/iq/index.json b/generated/iq/index.json index 4beaf5b..e7773e2 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -4484,7 +4484,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 4 } ] }, @@ -4499,7 +4500,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 1 } ] }, @@ -4530,7 +4532,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 3 } ] }, @@ -4570,7 +4573,8 @@ "method": "contentUint", "name": "content", "type": "integer", - "required": true + "required": true, + "byteLength": 3 }, { "method": "child", diff --git a/generated/manifest.json b/generated/manifest.json index e5d8814..b1f975a 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -62,12 +62,12 @@ "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "f6714888d1385cd244830338821e0529240eddb7296f588ef59abceacfe6e174" + "sha256": "b3870361f25c53e008716a39de46fca394c10a5b0a6ce697a91a3fa22f922b56" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "644607df3585822a944fe2dee3a08a7456d5cd00a7e5f07440a340051470dc86" + "sha256": "85d7eb9cc903d879fe88163de561c1e6a98f93b332b7a54905423cd119f619de" }, "mex": { "file": "mex/index.json", From 2ec36a998947275468403d3f5887281ba2041887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 08:37:05 -0300 Subject: [PATCH 26/37] scan: honour type="result", and stop counting losses the artifact never carries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 21 — five findings, three live in the committed artifacts. `variant_kind` short-circuited only on `type="error"`, so a parser asserting `type="result"` fell through to the name heuristic. `CreateResponseGroupAlreadyExists` and `RequestSilentNonceResponseRecoveryRequired` both assert `result` and shipped as `kind: "error"` — telling a consumer to route them opposite to the wire, in the one place this change claims the discriminator outranks the name. The wire wins now; an error-ish name on a `result` becomes `Alternative`, which is what that variant is for. `error` 47 → 45, `alternative` 0 → 2. `attach_pending_enum_keys` hung `enumKeys` on the companion `maybeAttrString` read without retyping it, so the incoming receipt's `type` shipped as `"string"` carrying nine `enumKeys` — invisible to a consumer selecting enum fields by `type == "enum"`. It is promoted to the enum accessor and type now. The incoming ack pass ran its own smax `Resolver` and discarded its drops, so the ack's `deprecatedEditMixin.edit` was an enum with no values while the domain reported nothing. Merging them naively gave 29 — but that pass parses every `WASmaxIn*Response*` and keeps only the `` roots, so 28 belonged to IQ modules absent from this artifact: the same "the diagnostic does not describe the file beside it" bug fixed last round on the legacy path. The collector is rewound for a module that contributes nothing, and the honest number is 1, matching the one lost constraint the artifact actually carries. Two more in the action reader: statements after an unconditional `return` were still scanned, so unreachable returns became additional legal actions; and `{actionType: "create"}` written as a literal resolved to `None`, publishing an arm with no identity. 542 tests, 12/12 schemas, determinism check clean. --- crates/wa-notif/src/actions.rs | 14 ++++++- crates/wa-notif/src/tests.rs | 31 +++++++++++++++- crates/wa-scan/src/incoming.rs | 31 +++++++++++++--- crates/wa-scan/src/response.rs | 55 +++++++++++++++++++++++++++- crates/wa-scan/src/response_index.rs | 51 ++++++++++++++++++++++++++ crates/wa-scan/src/response_smax.rs | 31 ++++++++++++++-- generated/incoming/index.json | 4 +- generated/iq/index.json | 4 +- generated/manifest.json | 8 ++-- 9 files changed, 210 insertions(+), 19 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index bc611ff..fafadfa 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -839,6 +839,11 @@ fn collect_returns<'b, 'a>( collect_branches(arg, &mut branches); out.extend(branches.into_iter().map(|e| (e, scope.clone()))); } + // Nothing after an unconditional `return` runs. Continuing to scan the + // list published unreachable returns as additional legal actions — + // `return {actionType: A}; return {actionType: B};` yielded both — and + // let statements below it contribute phantom fields. + return; } Statement::BlockStatement(b) => collect_returns(&as_refs(&b.body), &scope, out), Statement::IfStatement(i) => { @@ -1359,7 +1364,14 @@ fn fold_object<'b, 'a>( // leaving `actionType` and the constants on first-write was the same rule // written in three places again. if key == "actionType" { - def.action_type = ctx.consts.resolve(value); + // A shape may write the normalised identity directly (`{actionType: "create"}`) + // instead of through the constant table. The resolver only accepts + // `o("Mod").OBJECT.MEMBER`, so a fully static literal came back `None` and the + // arm published no `actionType` at all — indistinguishable from an identity + // the arm genuinely computes. + def.action_type = as_string_lit(value) + .map(str::to_string) + .or_else(|| ctx.consts.resolve(value)); continue; } if let Some(c) = const_value(value) { diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 4525024..36d15b4 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1282,6 +1282,11 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:combine(t.attrString("jid"), t.attrString("lid")), plain:t.attrString("plain")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.MULTI3: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:combine(t.attrString("jid"), t.attrString("jid"), t.attrString("lid")), plain:t.attrString("plain")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DEAD: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LIT: + return {actionType:"create", who:t.attrString("who")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1574,3 +1579,27 @@ fn a_conflict_past_the_second_wrapper_argument_still_refuses() { a.fields ); } + +#[test] +fn a_return_after_an_unconditional_return_is_unreachable() { + // `return A; return B;` — B never runs. Treating every syntactic return as reachable + // published it as a second legal action for the tag, and let statements below the + // return contribute fields too. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "dead") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!(got, ["first"], "only the reachable shape: {got:?}"); +} + +#[test] +fn a_literal_action_type_is_kept() { + // `{actionType: "create"}` is fully static. Resolving only `o("Mod").OBJECT.MEMBER` + // left the arm with no `actionType`, indistinguishable from one the arm computes. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "lit"); + assert_eq!(a.action_type.as_deref(), Some("create")); +} diff --git a/crates/wa-scan/src/incoming.rs b/crates/wa-scan/src/incoming.rs index a457284..576dcfc 100644 --- a/crates/wa-scan/src/incoming.rs +++ b/crates/wa-scan/src/incoming.rs @@ -93,8 +93,12 @@ pub fn scan_incoming_with_diagnostics( } } // The smax-parsed `` read-shapes (a client publish's server ack) — a distinct - // mechanism the legacy pass above never sees. Merged into the same catalog. - out.extend(scan_incoming_smax_acks(source, defs)); + // mechanism the legacy pass above never sees. Merged into the same catalog, and so + // are its losses: that pass runs its own smax `Resolver`, whose drops used to be + // discarded, so the ack's `deprecatedEditMixin.edit` shipped as an enum with no + // values while this domain's `dropsByReason` stayed empty. + let (acks, ack_drops) = scan_incoming_smax_acks(source, defs); + out.extend(acks); // Deterministic order, independent of bundle layout; then drop exact // (tag, parser, module) duplicates (a parser re-declared verbatim). out.sort_by(|a, b| { @@ -106,7 +110,11 @@ pub fn scan_incoming_with_diagnostics( out.dedup_by(|a, b| { a.tag == b.tag && a.module == b.module && a.shape.parser_name == b.shape.parser_name }); - (out, enums.into_drop_counts()) + let mut drops = enums.into_drop_counts(); + for (reason, n) in ack_drops { + *drops.entry(reason).or_default() += n; + } + (out, drops) } /// A `WASmaxIn*Response{Success,Negative}` module whose root reads a top-level `` — @@ -124,7 +132,10 @@ fn is_smax_ack_response_module(name: &str) -> bool { /// that asserts the top-level `ack`, with its field tree (ack envelope + edit/revoke/paid /// sub-mixins and disjunctions) resolved through the shared [`Resolver`]. Mirrors the /// srvreq root pass; only the tag gate (`ack`, not the server-request set) differs. -fn scan_incoming_smax_acks(source: &str, defs: &[ModuleDefinition]) -> Vec { +fn scan_incoming_smax_acks( + source: &str, + defs: &[ModuleDefinition], +) -> (Vec, std::collections::BTreeMap) { // Every module name → slice (first occurrence wins; shard dedup) so the resolver can // chase the cross-module ack mixins these roots fold in. let mut slices: HashMap<&str, &str> = HashMap::new(); @@ -141,7 +152,13 @@ fn scan_incoming_smax_acks(source: &str, defs: &[ModuleDefinition]) -> Vec` — the rest are IQ responses. Their losses must not be charged + // to this domain, so the collector is rewound when a module contributes nothing, + // the same rule the legacy pass follows by gating on the tag first. + let before = resolver.drops_snapshot(); let exports = analyze_module_exports(&source[m.start..m.end], &resolver); + let mut kept_any = false; for (name, shape) in &exports { // Skip inner child parsers: their export name extends another export's at a // word boundary (``), and their fields already nest in the @@ -174,14 +191,18 @@ fn scan_incoming_smax_acks(source: &str, defs: &[ModuleDefinition]) -> Vec { { if f.enum_keys.is_none() { f.enum_keys = Some(keys); + // Retype the companion read. The plain `maybeAttrString("type")` beside + // `attrEnumOrNullIfUnknown("type", map)` is the SAME wire attribute + // validated against that key set, so leaving it `type: "string"` while + // hanging nine `enumKeys` off it hid the constraint from every consumer + // that selects enum fields by `type == "enum"` — the incoming receipt's + // `type` shipped exactly that way. + let optional = wap::is_optional_method(&f.method); + f.method = if optional { + wap::MAYBE_ATTR_ENUM.to_string() + } else { + wap::ATTR_ENUM.to_string() + }; + f.field_type = ParsedFieldType::Enum; + f.required = !optional; } } else { // No companion plain read created a field for this attr (doesn't occur in @@ -760,7 +774,7 @@ impl ParserAnalyzer<'_, '_> { // optional attr validated against an enum key set, which is exactly // `maybeAttrEnum`. A raw "attrEnumOrNullIfUnknown" method would not be in // `wap::is_attr_method`, leaving the field unclassified downstream. - let mut f = mk_field(wap::MAYBE_ATTR_ENUM, &wire, ParsedFieldType::String, false); + let mut f = mk_field(wap::MAYBE_ATTR_ENUM, &wire, ParsedFieldType::Enum, false); f.enum_keys = Some(keys); self.fields.push(f); } @@ -1494,6 +1508,45 @@ mod tests { ); } + #[test] + fn a_companion_read_is_retyped_when_it_gains_enum_keys() { + // `attrEnumOrNullIfUnknown("type", map)` beside a plain `maybeAttrString("type")` + // is the SAME attribute validated against that key set. Hanging `enumKeys` off the + // companion while leaving it `type: "string"` hid the constraint from every + // consumer that selects enum fields by `type == "enum"`. + let module = r#"__d("M",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var u={delivery:1,read:2}; + var c=new(r("WADeprecatedWapParser"))("p", function(e){ + e.assertTag("receipt"); + var t=e.hasAttr("type")?e.attrEnumOrNullIfUnknown("type",u):0; + e.maybeAttrString("type"); + return {}; + }); + }),1);"#; + let out = parse_module_wap_parsers(module); + let p = out.iter().find(|r| r.parser_name == "p").expect("parser"); + let f = p + .fields + .iter() + .find(|f| f.name == "type" && f.tag.is_none()) + .expect("the type field"); + assert_eq!( + f.field_type, + ParsedFieldType::Enum, + "retyped, not just annotated" + ); + assert_eq!( + f.method, + wap::MAYBE_ATTR_ENUM, + "and under the enum accessor" + ); + assert!(!f.required, "the companion read was optional and stays so"); + assert_eq!( + f.enum_keys.as_deref(), + Some(["delivery", "read"].map(String::from).as_slice()) + ); + } + #[test] fn a_typed_content_accessor_becomes_a_typed_field() { // `contentUint` was outside the local attr/content list, so a read of it produced diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index d707bd8..0e61b68 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -491,6 +491,25 @@ fn variant_kind(tag: &str, assertions: &[wa_ir::ResponseAssertion]) -> ResponseV if asserts_error { return ResponseVariantKind::Error; } + // `type="result"` is the wire saying this arrived on the SUCCESS channel, and the + // discriminator outranks the name — the whole point of taking the root assertion + // first. Only `type="error"` was honoured, so `CreateResponseGroupAlreadyExists` and + // `RequestSilentNonceResponseRecoveryRequired` shipped as `kind: "error"` while + // asserting `type: "result"`, telling a consumer to route them opposite to the wire. + // + // An error-ish NAME on a `result` still means something: it is a structured + // non-happy outcome, not an ordinary success. That is what `Alternative` is for. + let asserts_result = assertions.iter().any(|a| { + a.kind == wa_ir::AssertionKind::Attr + && a.name.as_deref() == Some("type") + && a.value.as_deref() == Some("result") + }); + if asserts_result { + return match variant_kind_by_tag(tag) { + ResponseVariantKind::Success => ResponseVariantKind::Success, + _ => ResponseVariantKind::Alternative, + }; + } variant_kind_by_tag(tag) } @@ -533,6 +552,38 @@ fn response_op_name(module: &str) -> String { mod tests { use super::*; + fn attr(name: &str, value: &str) -> wa_ir::ResponseAssertion { + wa_ir::ResponseAssertion { + kind: wa_ir::AssertionKind::Attr, + name: Some(name.to_string()), + value: Some(value.to_string()), + reference_path: None, + } + } + + #[test] + fn an_asserted_result_outranks_an_error_name_token() { + // `CreateResponseGroupAlreadyExists` asserts `type="result"`: the wire says it + // arrived on the success channel, and the discriminator outranks the name. Only + // `type="error"` was honoured, so these shipped as `kind: "error"` and told a + // consumer to route them opposite to the wire. + let a = [attr("type", "result")]; + assert_eq!( + variant_kind("CreateResponseGroupAlreadyExists", &a), + ResponseVariantKind::Alternative, + "an error-ish name on a `result` is a structured non-happy outcome" + ); + assert_eq!( + variant_kind("CreateResponseSuccess", &a), + ResponseVariantKind::Success + ); + // The error discriminator still wins outright. + assert_eq!( + variant_kind("CreateResponseSuccess", &[attr("type", "error")]), + ResponseVariantKind::Error + ); + } + #[test] fn op_name_strips_prefix_suffix() { assert_eq!( diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 6f95d70..0ce2e61 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -118,10 +118,12 @@ impl<'a> Resolver<'a> { } } - /// The drops this resolver alone recorded, as counts. Production code reads the - /// shared collector through [`crate::response_index::ResponseIndex`] instead, since - /// the fallback resolver reports into the same place; this is the unit-test view. - #[cfg(test)] + /// The drops this resolver alone recorded, as counts. + /// + /// The IQ path reads the shared collector through + /// [`crate::response_index::ResponseIndex`] instead, since its fallback resolver + /// reports into the same place. The incoming ack pass owns its resolver outright and + /// reads it here — those drops were simply discarded before. pub(crate) fn drop_counts(&self) -> BTreeMap { self.drops .borrow() @@ -130,6 +132,27 @@ impl<'a> Resolver<'a> { .collect() } + /// A snapshot of the collector, so a caller that may REJECT the shape it just + /// analyzed can undo the drops that analysis recorded. + /// + /// A pass that parses more modules than it keeps (the incoming ack scan parses every + /// `WASmaxIn*Response*` and keeps only the ones asserting ``) would otherwise + /// charge the rejected modules' losses to a domain whose artifact never contains + /// them — a diagnostic that does not describe the file beside it. + pub(crate) fn drops_snapshot(&self) -> BTreeMap> { + self.drops.borrow().clone() + } + + /// Restore a [`drops_snapshot`], discarding everything recorded since. + /// + /// [`drops_snapshot`]: Resolver::drops_snapshot + pub(crate) fn restore_drops( + &self, + snapshot: BTreeMap>, + ) { + *self.drops.borrow_mut() = snapshot; + } + /// A resolver that reports its drops into an existing collector. pub(crate) fn with_drops(slices: &'a HashMap<&'a str, &'a str>, drops: Drops) -> Self { Self { diff --git a/generated/incoming/index.json b/generated/incoming/index.json index 99d73e1..c6ec76d 100644 --- a/generated/incoming/index.json +++ b/generated/incoming/index.json @@ -280,9 +280,9 @@ "required": false }, { - "method": "maybeAttrString", + "method": "maybeAttrEnum", "name": "type", - "type": "string", + "type": "enum", "required": false, "enumKeys": [ "delivery", diff --git a/generated/iq/index.json b/generated/iq/index.json index e7773e2..e07ddf4 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -11264,7 +11264,7 @@ { "tag": "RequestSilentNonceResponseRecoveryRequired", "moduleName": "WASmaxInBizAccessTokenRequestSilentNonceResponseRecoveryRequired", - "kind": "error", + "kind": "alternative", "assertions": [ { "kind": "tag", @@ -74331,7 +74331,7 @@ { "tag": "CreateResponseGroupAlreadyExists", "moduleName": "WASmaxInGroupsCreateResponseGroupAlreadyExists", - "kind": "error", + "kind": "alternative", "assertions": [ { "kind": "tag", diff --git a/generated/manifest.json b/generated/manifest.json index b1f975a..f666ad5 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -3,7 +3,9 @@ "appstateActions": 67, "diagnostics": { "incoming": { - "dropsByReason": {} + "dropsByReason": { + "response enum argument not structurally resolvable": 1 + } }, "iq": { "candidateModules": 207, @@ -62,12 +64,12 @@ "incoming": { "file": "incoming/index.json", "schema": "schema/incoming.schema.json", - "sha256": "b3870361f25c53e008716a39de46fca394c10a5b0a6ce697a91a3fa22f922b56" + "sha256": "5b25ff40ada72c298e17d8f8826c131b51b3c9d166e234dbb43f70647448d99b" }, "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "85d7eb9cc903d879fe88163de561c1e6a98f93b332b7a54905423cd119f619de" + "sha256": "05762f79b620e7506d629288d29a0fc7e1070263432fcc4cdb937ce8e467788c" }, "mex": { "file": "mex/index.json", From 5f10088c62eb61c7e8dab966db5e0d14d8b7505f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 08:54:53 -0300 Subject: [PATCH 27/37] scan: keep content enums reading content, and stop a rejected module poisoning the cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 22 — four findings. `normalize_accessor` rewrote `contentStringEnum` to `attrEnum`, so codegen — which switches on the method to choose between an attribute and the element body — emitted `get_attr(...)` for a value that is not an attribute. 12 fields in the committed IQ artifact read that way. They are `contentEnum` now, keeping the enum type. Last round's ack-drop rollback was not enough. It restored the diagnostic set but left the resolver's caches populated, so a rejected non-ack module could record a loss on a shared helper, have it rewound, and a later accepted ack would hit the cache and never record it again. The ack pass builds a resolver PER CANDIDATE now (88 candidates, 8 kept) — a discarded resolver takes its cache with it, so no rejected work is observable at all. The reported number is unchanged at 1. `new Uint8Array(4)` is a length, and therefore exactly four zero bytes — as much a compile-time constant as a literal array, and the request-side constant index already models it that way. Refusing it lost a recoverable pin. Bounded, so a bogus length cannot allocate. `emit_field_parse` routed every JID through the unconditional `ok_or_else("missing …")` branch while `rust_field_type` declares `Option` for a `maybeAttr…Jid` — a `Jid` initializer for an `Option` field, which also rejects the absence the accessor exists to permit. `union::field_expr` already had the optional branch; only this one diverged. That last one is *latent*, not live as reported: `iq.rs` has no `Option` field today and `notif.rs` emits no parser, so nothing miscompiles. It would the moment one appears — and CI would not catch it, since the generated Rust is only syntax-checked. That is follow-up 2 in the PR body, and this is the second finding in two rounds arguing for it. 545 tests, 12/12 schemas, determinism check clean. --- crates/wa-codegen/src/emit.rs | 6 +++ crates/wa-codegen/src/lib.rs | 49 +++++++++++++++++++++++ crates/wa-scan/src/incoming.rs | 27 ++++++++----- crates/wa-scan/src/response.rs | 15 +++++++ crates/wa-scan/src/response_smax.rs | 62 ++++++++++++++++------------- generated/iq/index.json | 24 +++++------ generated/manifest.json | 2 +- 7 files changed, 134 insertions(+), 51 deletions(-) diff --git a/crates/wa-codegen/src/emit.rs b/crates/wa-codegen/src/emit.rs index 8075637..f5f1786 100644 --- a/crates/wa-codegen/src/emit.rs +++ b/crates/wa-codegen/src/emit.rs @@ -62,6 +62,12 @@ pub(crate) fn emit_field_parse(f: &ParsedField, node_var: &str, indent: &str) -> format!("{indent} .as_str()"), format!("{indent} .parse()?;"), ], + // An OPTIONAL JID must come first: `rust_field_type` declares `Option` for a + // `maybeAttr…Jid`, and falling into the branch below both mis-typed the + // initializer and rejected the absence the accessor exists to permit. + t if t.is_jid() && optional => vec![format!( + "{indent}let {name} = {node_var}.get_attr({flit}).and_then(|v| v.to_jid());" + )], // Every JID flavor materializes as one `Jid`; switch on `is_jid()` so a newly // preserved flavor (UserJid/LidUserJid/…) is parsed as a JID, not a String. t if t.is_jid() => vec![format!( diff --git a/crates/wa-codegen/src/lib.rs b/crates/wa-codegen/src/lib.rs index e7af545..44ba592 100644 --- a/crates/wa-codegen/src/lib.rs +++ b/crates/wa-codegen/src/lib.rs @@ -375,6 +375,55 @@ mod tests { ); } + #[test] + fn an_optional_jid_parses_optionally() { + // `rust_field_type` declares `Option` for a `maybeAttr…Jid`, but every JID + // went through the unconditional branch — a `Jid` initializer for an + // `Option` field, which also rejects the absence the accessor permits. + let ir = IqIr { + wa_version: "0.0.0".into(), + stanzas: vec![IqStanzaDef { + module_name: "WAWebPn".into(), + namespace: "w:g2".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + parser_name: "p".into(), + exported_function: Some("pn".into()), + all_exports: vec!["pn".into()], + request: IqRequestDef { + namespace: "w:g2".into(), + iq_type: IqType::Get, + target: IqTarget::Server, + children: vec![], + }, + response: ParsedResponse { + parser_name: "p".into(), + assertions: vec![], + fields: vec![parsed( + "maybeAttrPhoneUserJid", + "participant_pn", + ParsedFieldType::UserJid, + )], + ..Default::default() + }, + }], + unparseable: vec![], + }; + let c = &generate_iq(&ir); + assert!( + c.contains("pub participant_pn: Option,"), + "declared optional:\n{c}" + ); + assert!( + !c.contains("missing participant_pn"), + "and never required in the parser:\n{c}" + ); + assert!( + c.contains(".and_then(|v| v.to_jid());"), + "parsed as an optional Jid:\n{c}" + ); + } + #[test] fn a_ranged_byte_content_child_is_still_bytes() { // `child("blob").contentBytesRange(1, 128)` decodes to bytes. Asking the diff --git a/crates/wa-scan/src/incoming.rs b/crates/wa-scan/src/incoming.rs index 576dcfc..1cf2f55 100644 --- a/crates/wa-scan/src/incoming.rs +++ b/crates/wa-scan/src/incoming.rs @@ -144,19 +144,24 @@ fn scan_incoming_smax_acks( .entry(m.name.as_str()) .or_insert(&source[m.start..m.end]); } - let resolver = Resolver::new(&slices); - let mut out = Vec::new(); let mut seen = HashSet::new(); + let mut drops: std::collections::BTreeMap = Default::default(); for m in defs { if !is_smax_ack_response_module(&m.name) || !seen.insert(m.name.as_str()) { continue; } - // This pass parses every `WASmaxIn*Response*` module and keeps only the roots - // asserting `` — the rest are IQ responses. Their losses must not be charged - // to this domain, so the collector is rewound when a module contributes nothing, - // the same rule the legacy pass follows by gating on the tag first. - let before = resolver.drops_snapshot(); + // A resolver PER CANDIDATE, because this pass parses every `WASmaxIn*Response*` + // (88 of them) and keeps only the roots asserting `` (8). The rest are IQ + // responses whose losses must not be charged to this domain. + // + // Rewinding a shared collector was not enough: the resolver also memoizes the + // helpers it resolved, so a rejected module could record a loss on a shared + // helper, have it rewound, and then a later accepted ack would hit the cache and + // never record it again — an unconstrained field with an empty diagnostic. A + // discarded resolver takes its cache with it, so no rejected work can be + // observed at all. + let resolver = Resolver::new(&slices); let exports = analyze_module_exports(&source[m.start..m.end], &resolver); let mut kept_any = false; for (name, shape) in &exports { @@ -198,11 +203,13 @@ fn scan_incoming_smax_acks( shape, }); } - if !kept_any { - resolver.restore_drops(before); + if kept_any { + for (reason, n) in resolver.drop_counts() { + *drops.entry(reason).or_default() += n; + } } } - (out, resolver.drop_counts()) + (out, drops) } #[cfg(test)] diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 7a9ac43..7e4abe3 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -1645,6 +1645,21 @@ mod tests { ); } + #[test] + fn a_zero_filled_uint8array_is_a_recoverable_pin() { + // `new Uint8Array(4)` is a LENGTH — and therefore exactly four zero bytes, as much + // a compile-time constant as a literal array. Refusing it lost a recoverable pin; + // reading the `4` as the byte 0x04 would have invented a different one. + let r = analyze_parser_ast( + r#"{ e.child("nonce").contentLiteralBytes(new Uint8Array(4)); }"#, + "e", + ); + let leaf = r.fields[0].children.as_ref().unwrap()[0].clone(); + assert_eq!(leaf.literal_value.as_deref(), Some("00000000")); + assert_eq!(leaf.byte_length, Some(4)); + assert!(r.unresolved.is_empty(), "and nothing is reported lost"); + } + #[test] fn an_unresolvable_byte_pin_is_reported_not_published_as_free_bytes() { let r = analyze_parser_ast(r#"{ e.child("t").contentLiteralBytes(computed()); }"#, "e"); diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 0ce2e61..35d9b4b 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -132,27 +132,6 @@ impl<'a> Resolver<'a> { .collect() } - /// A snapshot of the collector, so a caller that may REJECT the shape it just - /// analyzed can undo the drops that analysis recorded. - /// - /// A pass that parses more modules than it keeps (the incoming ack scan parses every - /// `WASmaxIn*Response*` and keeps only the ones asserting ``) would otherwise - /// charge the rejected modules' losses to a domain whose artifact never contains - /// them — a diagnostic that does not describe the file beside it. - pub(crate) fn drops_snapshot(&self) -> BTreeMap> { - self.drops.borrow().clone() - } - - /// Restore a [`drops_snapshot`], discarding everything recorded since. - /// - /// [`drops_snapshot`]: Resolver::drops_snapshot - pub(crate) fn restore_drops( - &self, - snapshot: BTreeMap>, - ) { - *self.drops.borrow_mut() = snapshot; - } - /// A resolver that reports its drops into an existing collector. pub(crate) fn with_drops(slices: &'a HashMap<&'a str, &'a str>, drops: Drops) -> Self { Self { @@ -1280,7 +1259,11 @@ fn normalize_accessor(m: &str) -> Option<(String, ParsedFieldType, Option)> let canonical = match m { "attrStanzaId" | "attrCallId" | "attrStringFromReference" => wap::ATTR_STRING, "attrIntRange" => wap::ATTR_INT, - "attrStringEnum" | "contentStringEnum" => wap::ATTR_ENUM, + "attrStringEnum" => wap::ATTR_ENUM, + // A content enum must keep a CONTENT spelling: codegen switches on the method to + // decide whether to read an attribute or the element body, so rewriting this to + // `attrEnum` made the generated parser look for an attribute that does not exist. + "contentStringEnum" => "contentEnum", "contentBytesRange" => wap::CONTENT_BYTES, "attrPhoneUserJid" => wap::ATTR_USER_JID, "attrPhoneDeviceJid" => wap::ATTR_DEVICE_JID, @@ -2088,6 +2071,11 @@ impl Resolver<'_> { /// /// Shared with the legacy scanner, which reaches `contentLiteralBytes` by a different /// route and must pin the same value from the same spelling. +/// Upper bound on a `new Uint8Array(N)` zero-fill we will materialize as a literal. A +/// pin longer than this is not a wire constant anyone writes; the cap keeps a bogus or +/// hostile length from allocating. +const MAX_ZERO_FILL: i64 = 4096; + pub(crate) fn static_byte_literal(e: &Expression) -> Option> { // `Uint8Array.of(1, 2)` — the bytes are the ARGUMENTS, not an array operand. It was // documented as supported here and was not: every call expression fell through to @@ -2108,17 +2096,22 @@ pub(crate) fn static_byte_literal(e: &Expression) -> Option> { let elements = match e { Expression::ArrayExpression(a) => a, Expression::NewExpression(n) => { - // `new Uint8Array([...])` — the sequence is the sole argument. `new - // Uint8Array(4)` (a LENGTH, not a value) deliberately does not match: it pins - // no bytes, and reading `4` as the byte 0x04 would invent a constraint. The - // constructor is checked for the same reason the `.of` receiver is: `new - // Whatever([1,2])` need not produce those bytes. + // The constructor is checked for the same reason the `.of` receiver is: + // `new Whatever([1,2])` need not produce those bytes. if as_identifier(&n.callee) != Some("Uint8Array") { return None; } match arg_expr(n.arguments.first()?)? { Expression::ArrayExpression(a) => a, - _ => return None, + // `new Uint8Array(4)` is a LENGTH — and therefore exactly four ZERO + // bytes, which is as much a compile-time constant as a literal array. + // Refusing it lost a recoverable pin; reading the `4` as the byte 0x04 + // would have invented a different one. The request side's constant index + // already models this form the same way. + e => { + let n = as_int(e).filter(|n| (0..=MAX_ZERO_FILL).contains(n))?; + return Some(vec![0u8; n as usize]); + } } } _ => return None, @@ -2161,6 +2154,19 @@ mod tests { analyze_module_exports(module, &resolver) } + #[test] + fn a_content_enum_keeps_a_content_spelling() { + // `contentStringEnum(node, TABLE)` reads the ELEMENT BODY. Normalizing it to + // `attrEnum` made codegen emit an attribute read for a value that is not an + // attribute, so the generated parser looked for something that never exists. + assert_eq!( + normalize_accessor("contentStringEnum").map(|(m, t, _)| (m, t)), + Some(("contentEnum".to_string(), wa_ir::ParsedFieldType::Enum)), + "content spelling preserved, enum type kept" + ); + assert!(wa_ir::wap::is_content_method("contentEnum")); + } + #[test] fn attrs_and_literal_assertion() { // attrString → field; literal(...) → assertion, no field; type from binding. diff --git a/generated/iq/index.json b/generated/iq/index.json index e07ddf4..92e3e9f 100644 --- a/generated/iq/index.json +++ b/generated/iq/index.json @@ -9139,7 +9139,7 @@ "tag": "token_type", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "elementValue", "type": "enum", "required": true, @@ -9244,7 +9244,7 @@ "tag": "token_type", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "elementValue", "type": "enum", "required": true, @@ -10222,7 +10222,7 @@ "required": false, "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "showOnProfileElementValue", "type": "enum", "required": true, @@ -10399,7 +10399,7 @@ "required": false, "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "showOnProfileElementValue", "type": "enum", "required": true, @@ -10716,7 +10716,7 @@ "required": false, "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "showOnProfileElementValue", "type": "enum", "required": true, @@ -10893,7 +10893,7 @@ "required": false, "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "showOnProfileElementValue", "type": "enum", "required": true, @@ -11588,7 +11588,7 @@ "tag": "Result", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "statusElementValue", "type": "enum", "required": true, @@ -11668,7 +11668,7 @@ "tag": "Result", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "statusElementValue", "type": "enum", "required": true, @@ -96619,7 +96619,7 @@ "tag": "wf_deleted", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "elementValue", "type": "enum", "required": true, @@ -96759,7 +96759,7 @@ "tag": "wf_deleted", "children": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "elementValue", "type": "enum", "required": true, @@ -97805,7 +97805,7 @@ "assertions": [], "fields": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "nprElementValue", "type": "enum", "required": true, @@ -97868,7 +97868,7 @@ ], "fields": [ { - "method": "attrEnum", + "method": "contentEnum", "name": "nprElementValue", "type": "enum", "required": true, diff --git a/generated/manifest.json b/generated/manifest.json index f666ad5..98fc7ed 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -69,7 +69,7 @@ "iq": { "file": "iq/index.json", "schema": "schema/iq.schema.json", - "sha256": "05762f79b620e7506d629288d29a0fc7e1070263432fcc4cdb937ce8e467788c" + "sha256": "455d2a98f9e9ead8311bd8b5fc2cbadfabf6247ce699b5fa5d36f3bb796b3a28" }, "mex": { "file": "mex/index.json", From cb533c830329f24807ad21e1c8e2aaee9b202fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 09:17:56 -0300 Subject: [PATCH 28/37] notif: recover the fields a value-normalising helper was eating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 23 — five findings, two of them live. `fold_object` gives a module-local helper call first claim on its key. When the helper only NORMALISES a value handed to it — `reason: hasAttr("reason") ? S(t.attrString( "reason")) : null`, where `S` maps the string — its body reads no wire attribute of its own, so inlining produced an empty shape and the key was dropped. `add`, `remove` and `delete` lost their `reason` that way while `create`, whose wrapper is not a known local, kept it. An empty inline now falls through to `read_field`, which finds the read in the CALL's arguments and refuses the key outright if two arguments disagree. Shape elements 181 → 184. The legacy enum-drop key was `site + table + attr` with no traversal path, so one parser losing the same table for the same attribute under two children counted once. IQ's `dropsByReason` was undercounting: 37 → 41. Every other per-occurrence key fixed in this review was preventive; this one was hiding four real losses. Three more, all latent: * a `finally` was analyzed against the pre-`try` scope, so a name the `try` reassigned published its stale source. The body may also have thrown part-way, so neither binding is certain — every name `try`/`catch` writes is tombstoned for the finalizer. * loop bodies were never traversed, so `while (c) { return A } return B` emitted only B. * the mapped-child callback pre-installed the whole body's `var` initializers, making a later one visible to an earlier return — the exact thing `collect_returns` documents itself as avoiding. Removed; statement order already covers it, and the artifact does not move, so the pre-pass was contributing nothing but the bug. 548 tests, 12/12 schemas, determinism check clean. --- crates/wa-notif/src/actions.rs | 130 ++++++++++++++++---------------- crates/wa-notif/src/tests.rs | 59 ++++++++++++++- crates/wa-scan/src/enum_link.rs | 24 +++++- generated/manifest.json | 6 +- generated/notif/index.json | 26 ++++++- 5 files changed, 170 insertions(+), 75 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index fafadfa..9cbb18c 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -875,7 +875,15 @@ fn collect_returns<'b, 'a>( // empty. Only a finalizer that leaves on EVERY path replaces them. let mut finalizer = Vec::new(); if let Some(f) = &t.finalizer { - collect_returns(&as_refs(&f.body), &scope, &mut finalizer); + // The finalizer runs AFTER the try body, so the entry scope is wrong: + // `var x = a("jid"); try { x = a("lid") } finally { return {id: x} }` + // returns `lid`. The body may also have thrown part-way, so neither + // binding is certain — every name the try or catch writes is + // tombstoned for the finalizer, the same conservative merge used + // where branches rejoin. + let mut fin_scope = scope.clone(); + tombstone_branch_writes(s, &mut fin_scope); + collect_returns(&as_refs(&f.body), &fin_scope, &mut finalizer); } let finalizer_settles = t .finalizer @@ -900,11 +908,37 @@ fn collect_returns<'b, 'a>( } tombstone_branch_writes(s, &mut scope); } + // A loop body is a conditional path like any other: `while (c) { return A } + // return B` legally produces both, and falling into the catch-all collected + // only B. Its writes are tombstoned on exit for the same reason a branch's + // are — the body may have run any number of times, including zero. + Statement::ForStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) + | Statement::WhileStatement(_) + | Statement::DoWhileStatement(_) => { + if let Some(body) = loop_body(s) { + collect_returns(&[body], &scope, out); + } + tombstone_branch_writes(s, &mut scope); + } _ => {} } } } +/// The body statement of any loop form, so one arm can handle them all. +fn loop_body<'b, 'a>(s: &'b Statement<'a>) -> Option<&'b Statement<'a>> { + match s { + Statement::ForStatement(f) => Some(&f.body), + Statement::ForInStatement(f) => Some(&f.body), + Statement::ForOfStatement(f) => Some(&f.body), + Statement::WhileStatement(w) => Some(&w.body), + Statement::DoWhileStatement(d) => Some(&d.body), + _ => None, + } +} + /// The assignment operands of an expression, flattening the minifier's comma sequences. fn flatten_sequence<'b, 'a>( e: &'b Expression<'a>, @@ -976,6 +1010,11 @@ fn assigned_names<'a>(s: &'a Statement, out: &mut Vec<&'a str>) { assigned_names(a, out); } } + s if loop_body(s).is_some() => { + if let Some(b) = loop_body(s) { + assigned_names(b, out); + } + } Statement::TryStatement(t) => { t.block.body.iter().for_each(|s| assigned_names(s, out)); if let Some(h) = &t.handler { @@ -1027,66 +1066,6 @@ type Scope<'b, 'a> = HashMap<&'b str, &'b Expression<'a>>; /// A result shape together with the bindings in force where it is returned. type Shape<'b, 'a> = (&'b Expression<'a>, Scope<'b, 'a>); -/// A single flat scope for callers that have no return path to attribute a binding to -/// (the object-property walker visits every property in a callback at once). -/// -/// Without a path, a name rebound to a *different* initializer in a sibling branch -/// cannot be resolved correctly — so it is refused rather than guessed, the same -/// "missing, not wrong" rule the constant tables and helper names follow. -fn scope_bindings<'b, 'a>(stmts: &'b [Statement<'a>]) -> Scope<'b, 'a> { - let mut out = Scope::new(); - let mut ambiguous: Vec<&str> = Vec::new(); - collect_bindings(stmts, &mut out, &mut ambiguous); - for name in ambiguous { - out.remove(name); - } - out -} - -/// Collect `var` bindings through control flow, mirroring [`collect_returns`]. -/// -/// `var` is function-scoped in JS, so a declaration inside an `if` block is in scope for -/// the whole function — and now that returns are collected from nested branches, their -/// locals have to be too, or `if (c) { var id = child.attrString("id"); return {id} }` -/// yields a return whose `id` resolves to nothing and is silently dropped. First binding -/// wins, so the walk order (source order) decides, not the recursion order. -fn collect_bindings<'b, 'a>( - stmts: &'b [Statement<'a>], - out: &mut Scope<'b, 'a>, - ambiguous: &mut Vec<&'b str>, -) { - for s in stmts { - match s { - Statement::VariableDeclaration(decl) => { - for d in &decl.declarations { - if let (Some(name), Some(init)) = (d.id.get_identifier_name(), d.init.as_ref()) - { - match out.entry(name.as_str()) { - std::collections::hash_map::Entry::Vacant(e) => { - e.insert(init); - } - std::collections::hash_map::Entry::Occupied(e) => { - if !std::ptr::eq(*e.get(), init) { - ambiguous.push(name.as_str()); - } - } - } - } - } - } - Statement::BlockStatement(b) => collect_bindings(&b.body, out, ambiguous), - Statement::IfStatement(i) => { - collect_bindings(std::slice::from_ref(&i.consequent), out, ambiguous); - if let Some(alt) = &i.alternate { - collect_bindings(std::slice::from_ref(alt), out, ambiguous); - } - } - Statement::TryStatement(t) => collect_bindings(&t.block.body, out, ambiguous), - _ => {} - } - } -} - /// Follow a bare identifier through the scope to the expression bound to it. Bounded so /// a self-referential minified binding can't loop. fn deref_ident<'b, 'a>(e: &'b Expression<'a>, scope: &Scope<'b, 'a>) -> &'b Expression<'a> { @@ -1389,6 +1368,22 @@ fn fold_object<'b, 'a>( { let mut nested = empty_action(String::new()); inline_local(&src, &mut nested, ctx, depth); + // A helper that yields nothing is a helper the inliner cannot see through — + // typically because the value arrives through a PARAMETER + // (`reason: hasAttr("reason") ? S(t.attrString("reason")) : null`, where `S` + // normalises the string it is handed). Its body reads no wire attribute of its + // own, so inlining produced an empty shape and the key was dropped, live for + // `add`, `remove` and `delete`'s `reason`. + // + // Falling through to `read_field` recovers it: the wire read is in the CALL's + // arguments, which `find_accessor` already descends into — and it refuses the + // key outright if two arguments disagree. + if nested.children.is_empty() && nested.fields.is_empty() { + if let Some(field) = read_field(key, value, scope, ctx.consts) { + write_key(def, key, KeyValue::Field(field)); + } + continue; + } // A helper whose result is a repeated element becomes this key's child list; // one that returns a flat object contributes its fields under their own names. for mut c in nested.children { @@ -1762,12 +1757,13 @@ fn collect_accessor_fields<'b, 'a>( consts: &ConstResolver, out: &mut Vec, ) { - // The callback's own `var` bindings, layered over the enclosing scope: the minifier - // hoists most reads into locals inside the callback too. - let mut scope = outer.clone(); - if let Some(stmts) = function_body_of(e) { - scope.extend(scope_bindings(stmts)); - } + // No whole-body pre-pass. `collect_returns` installs the callback's own `var` + // bindings in STATEMENT ORDER, which is the same rule it already documents for the + // top level: hoisting moves the declaration, not the assignment, so snapshotting the + // whole body made a later initializer visible to an earlier return — + // `if (c) return {id: x}; var x = item.attrString("late")` published a wire + // dependency on `late` for a branch that runs before it exists. + let scope = outer.clone(); // Only function arguments hold the per-element shape; a bare expression contributes // nothing (the tag string, the bounds). if !matches!( diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 36d15b4..8111509 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,13 +1229,14 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); }), 1); __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ function hlp(e){ return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, extra:e.attrString("extra")}; } + function norm(v){ return v == null ? null : v; } function h(e){ var x=e.mapChildrenWithTag("child", function(t){ switch (t.tag) { @@ -1287,6 +1288,15 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LIT: return {actionType:"create", who:t.attrString("who")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LOOP: + while (t.hasChild("more")) { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, l:t.attrString("l")}; } + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, m:t.attrString("m")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FINTRY: { + var fx = t.attrString("jid"); + try { fx = t.attrString("lid"); } finally { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, id:fx}; } + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.PARAM: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, reason:t.hasAttr("reason")?norm(t.attrString("reason")):null}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1603,3 +1613,50 @@ fn a_literal_action_type_is_kept() { let a = edge_action(&ir, "lit"); assert_eq!(a.action_type.as_deref(), Some("create")); } + +#[test] +fn a_loop_body_return_is_collected() { + // `while (c) { return A } return B` legally produces both. Loops fell into the + // catch-all, so only B was emitted. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let mut got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "loop_tag") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + got.sort_unstable(); + assert_eq!(got, ["first", "second"], "both paths: {got:?}"); +} + +#[test] +fn a_finalizer_does_not_read_a_binding_the_try_overwrote() { + // `var x = a("jid"); try { x = a("lid") } finally { return {id: x} }` returns `lid`. + // Analyzing the finalizer against the pre-`try` scope published `jid` as fact; the + // body may also have thrown part-way, so neither source is certain and the name is + // tombstoned rather than guessed. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "fintry"); + assert!( + !a.fields.iter().any(|f| f.wire_name == "jid"), + "the stale pre-try source must not be published: {:?}", + a.fields + ); +} + +#[test] +fn a_helper_that_only_normalises_its_argument_keeps_the_wire_read() { + // `reason: hasAttr("reason") ? norm(t.attrString("reason")) : null` — `norm` is a + // module-local helper, so the inliner claimed the key, produced an empty shape + // (the helper reads no attribute of its own) and dropped the field. Live for + // `add`/`remove`/`delete`'s `reason` before the fallback to `read_field`. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "param"); + let f = a + .fields + .iter() + .find(|f| f.name == "reason") + .expect("the field survives the helper"); + assert_eq!(f.wire_name, "reason"); + assert!(!f.required, "it sits behind a presence guard"); +} diff --git a/crates/wa-scan/src/enum_link.rs b/crates/wa-scan/src/enum_link.rs index 92271a0..b1732ac 100644 --- a/crates/wa-scan/src/enum_link.rs +++ b/crates/wa-scan/src/enum_link.rs @@ -110,6 +110,20 @@ impl<'a> EnumResolver<'a> { site: &str, fields: &mut [wa_ir::ParsedField], dropped: &mut std::collections::BTreeSet, + ) { + self.resolve_fields_at(site, "", fields, dropped) + } + + /// [`resolve_fields`], carrying the traversal `path` so two losses of the same table + /// on the same attribute under different children count as two. + /// + /// [`resolve_fields`]: EnumResolver::resolve_fields + fn resolve_fields_at( + &mut self, + site: &str, + path: &str, + fields: &mut [wa_ir::ParsedField], + dropped: &mut std::collections::BTreeSet, ) { for f in fields { // Taken, not read: the pending reference is consumed here whether or not it @@ -131,14 +145,18 @@ impl<'a> EnumResolver<'a> { wa_ir::PendingEnum::Unresolvable => Some("".to_string()), }; if let Some(what) = lost { - dropped.insert(format!("{site}\u{1}{what}@{attr}")); + // The node PATH is part of the identity: one parser reading `state` + // off both `` and `` loses two constraints, and + // `site + table + attr` collapsed them into one. + dropped.insert(format!("{site}\u{1}{path}/{what}@{attr}")); } } + let here = format!("{path}/{}", f.tag.as_deref().unwrap_or(&f.name)); if let Some(children) = &mut f.children { - self.resolve_fields(site, children, dropped); + self.resolve_fields_at(site, &here, children, dropped); } for uv in f.union_variants.iter_mut().flatten() { - self.resolve_fields(site, &mut uv.fields, dropped); + self.resolve_fields_at(site, &here, &mut uv.fields, dropped); } } } diff --git a/generated/manifest.json b/generated/manifest.json index 98fc7ed..5723f44 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -26,7 +26,7 @@ "dropsByReason": { "iq builder substring present but no AST iq call": 5, "mixin fragment (folded into requests, not a standalone stanza)": 66, - "response enum argument not structurally resolvable": 37 + "response enum argument not structurally resolvable": 41 }, "excludedFragments": 66, "stanzas": 143, @@ -34,7 +34,7 @@ "unparseable": 5 }, "notif": { - "actionShapes": 181, + "actionShapes": 184, "actions": 47, "degraded": 5, "dropsByReason": { @@ -79,7 +79,7 @@ "notif": { "file": "notif/index.json", "schema": "schema/notif.schema.json", - "sha256": "a4f868868e9689488e4dcbf58ebb09665421626acdbef1ee9ceab27d4f946883" + "sha256": "473f4d20fefe3136b264f3398811fb9af85dc5fca59022b727ca16f83420cd33" }, "proto": { "file": "proto/WAProto.proto", diff --git a/generated/notif/index.json b/generated/notif/index.json index db24569..2616b04 100644 --- a/generated/notif/index.json +++ b/generated/notif/index.json @@ -1779,6 +1779,14 @@ { "wireTag": "add", "actionType": "add", + "fields": [ + { + "name": "reason", + "wireName": "reason", + "type": "string", + "required": false + } + ], "children": [ { "name": "participants", @@ -1832,11 +1840,27 @@ }, { "wireTag": "delete", - "actionType": "delete" + "actionType": "delete", + "fields": [ + { + "name": "reason", + "wireName": "reason", + "type": "string", + "required": false + } + ] }, { "wireTag": "remove", "actionType": "remove", + "fields": [ + { + "name": "reason", + "wireName": "reason", + "type": "string", + "required": false + } + ], "children": [ { "name": "participants", From 276b255b8ad460c0e5673ddfe45ca9a4d6b244e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 09:40:18 -0300 Subject: [PATCH 29/37] notif: emit the payload action unions in the reference codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 24 — five threads, four distinct findings. `generated/notif/notif.rs` described the notification ENVELOPE and nothing inside it, so a consumer using the advertised reference catalog instead of the JSON saw none of the 47 `w:gp2` arms this PR recovers. `NOTIF_ACTIONS` now carries every arm with its wire tag, its normalized `actionType`, its fields, the constants it stamps and its mapped children, plus an `actions_for(type)` lookup — emitted as data rather than 47 generated struct names, since the arms are a many-to-one map with heterogeneous payloads. The `not_ephemeral → ephemeral` + `duration: 0` normalization, the example the PR body leads with, is now in the generated Rust. 512 → 605 lines. `ModuleScopeBuilder` recorded enum tables at ANY function depth, first-wins, while the helper branch beside it correctly required the factory body. Two parsers reusing one minified local name would therefore share a table, and the second `u.members()` resolved to the FIRST parser's values — a closed `enumKeys` set that is simply wrong. Invented constraints are the worst outcome this change can produce, so the gate now matches the helper branch. No artifact movement; the tables that resolve today are all module-scope. Two more in the action reader, both latent: * a bare block always runs and `var` is function-scoped, so `{ x = a("lid"); } return {id: x}` reads `lid` — analyzing it against a clone discarded the write. Blocks now hand their scope back; branch bodies still clone, deliberately. * `if (c) return A; else return B; return C;` emitted C as a third legal action. The direct-`return` stop added last round is now the general "this statement exits on every path" check, which the existing analysis already knew how to answer. 551 tests, 12/12 schemas, determinism check clean. --- crates/wa-codegen/src/notif_export.rs | 157 +++++++++++++++++++++++++- crates/wa-notif/src/actions.rs | 50 +++++--- crates/wa-notif/src/tests.rs | 48 +++++++- crates/wa-scan/src/response.rs | 9 ++ 4 files changed, 245 insertions(+), 19 deletions(-) diff --git a/crates/wa-codegen/src/notif_export.rs b/crates/wa-codegen/src/notif_export.rs index 9bc64b3..1beb0b9 100644 --- a/crates/wa-codegen/src/notif_export.rs +++ b/crates/wa-codegen/src/notif_export.rs @@ -9,7 +9,7 @@ use wa_ir::{NotifIr, NotificationDef, StanzaTagDef}; use crate::fields::{RustChildStruct, RustField, collect_response_fields, emit_enum_def}; -use crate::naming::{pascal_case, snake_case}; +use crate::naming::{pascal_case, rust_lit, snake_case}; /// Generate the reference Rust catalog from the notification IR. pub fn generate_notif(ir: &NotifIr) -> String { @@ -17,6 +17,7 @@ pub fn generate_notif(ir: &NotifIr) -> String { body.push_str(&emit_notification_type_enum(&ir.notifications)); body.push_str(&emit_stanza_tag_enum(&ir.stanza_tags)); body.push_str(&emit_handler_table(&ir.notifications)); + body.push_str(&emit_action_tables(&ir.notifications)); let (content, uses_jid) = emit_content_module(&ir.notifications); let mut out = String::new(); @@ -266,6 +267,129 @@ fn opt_str(v: &Option) -> String { } } +/// The payload action unions (`notifications[].actions`) as a const table. +/// +/// The envelope structs above describe what wraps the payload; this is the layer INSIDE +/// it — `w:gp2`'s 47 group-action arms and their normalization. Without it a consumer +/// reading the reference `notif.rs` instead of the JSON sees none of it, which is the +/// whole point of shipping a reference consumer. +/// +/// Emitted as data rather than a generated enum per notification type: the arms are a +/// many-to-one wire-tag → action-type map with heterogeneous payloads, and a table keeps +/// the mapping and the per-arm shape addressable without inventing 47 struct names. +fn emit_action_tables(notifications: &[NotificationDef]) -> String { + if notifications.iter().all(|n| n.actions.is_empty()) { + return String::new(); + } + let mut l = String::new(); + l.push_str("/// One field an action arm reads off its child element.\n"); + l.push_str("#[derive(Debug, Clone, Copy)]\n"); + l.push_str("pub struct NotifActionField {\n"); + l.push_str(" /// Output field name.\n"); + l.push_str(" pub name: &'static str,\n"); + l.push_str(" /// The wire attribute (or child tag, for a content read) it comes from.\n"); + l.push_str(" pub wire_name: &'static str,\n"); + l.push_str(" /// Whether the arm reads it unconditionally.\n"); + l.push_str(" pub required: bool,\n"); + l.push_str(" /// The element body is read instead of an attribute.\n"); + l.push_str(" pub content: bool,\n"); + l.push_str("}\n\n"); + l.push_str("/// A repeated sub-element an arm maps over (`participants`).\n"); + l.push_str("#[derive(Debug, Clone, Copy)]\n"); + l.push_str("pub struct NotifActionChild {\n"); + l.push_str(" pub name: &'static str,\n"); + l.push_str(" pub wire_tag: &'static str,\n"); + l.push_str(" pub fields: &'static [NotifActionField],\n"); + l.push_str("}\n\n"); + l.push_str("/// One arm of a notification's payload action union.\n"); + l.push_str("#[derive(Debug, Clone, Copy)]\n"); + l.push_str("pub struct NotifAction {\n"); + l.push_str(" pub notif_type: NotificationType,\n"); + l.push_str(" /// The child element's tag — what the handler switches on.\n"); + l.push_str(" pub wire_tag: &'static str,\n"); + l.push_str( + " /// The normalized identity. NOT always the wire tag (`not_ephemeral` →\n\ + \x20 /// `ephemeral`); `None` when the arm computes it.\n", + ); + l.push_str(" pub action_type: Option<&'static str>,\n"); + l.push_str(" pub fields: &'static [NotifActionField],\n"); + l.push_str( + " /// Fields the arm stamps to a constant rather than reading — the\n\ + \x20 /// normalization that is invisible from the wire alone.\n", + ); + l.push_str(" pub constants: &'static [(&'static str, &'static str)],\n"); + l.push_str(" pub children: &'static [NotifActionChild],\n"); + l.push_str("}\n\n"); + + let field_list = |fields: &[wa_ir::NotifActionField]| -> String { + let items: Vec = fields + .iter() + .map(|f| { + format!( + "NotifActionField {{ name: {}, wire_name: {}, required: {}, content: {} }}", + rust_lit(&f.name), + rust_lit(&f.wire_name), + f.required, + f.content + ) + }) + .collect(); + format!("&[{}]", items.join(", ")) + }; + + l.push_str("/// Every payload action arm, by notification type then wire tag.\n"); + l.push_str("pub const NOTIF_ACTIONS: &[NotifAction] = &[\n"); + for n in notifications { + for a in &n.actions { + let constants: Vec = a + .constant_fields + .iter() + .map(|c| { + let v = match &c.value { + wa_ir::NotifConstValue::Bool(b) => b.to_string(), + wa_ir::NotifConstValue::Int(i) => i.to_string(), + wa_ir::NotifConstValue::Str(s) => s.clone(), + }; + format!("({}, {})", rust_lit(&c.name), rust_lit(&v)) + }) + .collect(); + let children: Vec = a + .children + .iter() + .map(|c| { + format!( + "NotifActionChild {{ name: {}, wire_tag: {}, fields: {} }}", + rust_lit(&c.name), + rust_lit(&c.wire_tag), + field_list(&c.fields) + ) + }) + .collect(); + l.push_str(&format!( + " NotifAction {{ notif_type: NotificationType::{}, wire_tag: {}, \ + action_type: {}, fields: {}, constants: &[{}], children: &[{}] }},\n", + pascal_case(&n.notif_type), + rust_lit(&a.wire_tag), + match &a.action_type { + Some(t) => format!("Some({})", rust_lit(t)), + None => "None".to_string(), + }, + field_list(&a.fields), + constants.join(", "), + children.join(", ") + )); + } + } + l.push_str("];\n\n"); + l.push_str( + "/// The arms a notification type's payload can carry.\n\ + pub fn actions_for(t: NotificationType) -> impl Iterator {\n\ + \x20 NOTIF_ACTIONS.iter().filter(move |a| a.notif_type == t)\n\ + }\n\n", + ); + l +} + #[cfg(test)] mod tests { use super::*; @@ -274,6 +398,37 @@ mod tests { SubDiscriminant, SubDiscriminantOn, }; + #[test] + fn the_payload_action_union_reaches_generated_rust() { + // The envelope structs describe what WRAPS the payload; the arms inside it are a + // whole protocol layer, and a consumer reading this file instead of the JSON saw + // none of it. The many-to-one normalization is the part that cannot be guessed + // from the wire, so it is what the assertion pins. + let mut ir = ir(); + ir.notifications[0].actions = vec![wa_ir::NotifActionDef { + wire_tag: "not_ephemeral".into(), + action_type: Some("ephemeral".into()), + fields: vec![], + constant_fields: vec![wa_ir::NotifActionConstant { + name: "duration".into(), + value: wa_ir::NotifConstValue::Int(0), + }], + children: vec![], + }]; + let src = generate_notif(&ir); + assert!(src.contains("pub const NOTIF_ACTIONS"), "the table exists"); + assert!( + src.contains(r#"wire_tag: "not_ephemeral""#) + && src.contains(r#"action_type: Some("ephemeral")"#), + "the many-to-one mapping survives:\n{src}" + ); + assert!( + src.contains(r#"("duration", "0")"#), + "and the constant the arm stamps:\n{src}" + ); + syn::parse_file(&src).expect("generated notif.rs is valid Rust"); + } + fn ir() -> NotifIr { NotifIr { wa_version: "2.3000.test".into(), diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 9cbb18c..f73cca7 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -797,6 +797,16 @@ fn collect_returns<'b, 'a>( // one branch's return read the other branch's attribute — a wrong `wireName` rather // than a missing field. let mut scope = outer.clone(); + collect_returns_into(stmts, &mut scope, out); +} + +/// [`collect_returns`] over a scope the caller owns, so a construct that always runs — +/// a bare block — can hand its writes back instead of having them discarded with a clone. +fn collect_returns_into<'b, 'a>( + stmts: &[&'b Statement<'a>], + scope: &mut Scope<'b, 'a>, + out: &mut Vec>, +) { for s in stmts.iter().copied() { match s { Statement::VariableDeclaration(decl) => { @@ -824,7 +834,7 @@ fn collect_returns<'b, 'a>( flatten_sequence(&e.expression, &mut ops); for a in ops { if let Some(name) = a.left.get_identifier_name() { - let reads_wire = find_accessor(strip_guard(&a.right), &scope).is_some(); + let reads_wire = find_accessor(strip_guard(&a.right), scope).is_some(); if reads_wire { scope.insert(name, &a.right); } else { @@ -839,24 +849,24 @@ fn collect_returns<'b, 'a>( collect_branches(arg, &mut branches); out.extend(branches.into_iter().map(|e| (e, scope.clone()))); } - // Nothing after an unconditional `return` runs. Continuing to scan the - // list published unreachable returns as additional legal actions — - // `return {actionType: A}; return {actionType: B};` yielded both — and - // let statements below it contribute phantom fields. - return; } - Statement::BlockStatement(b) => collect_returns(&as_refs(&b.body), &scope, out), + // A bare block ALWAYS runs and `var` is function-scoped, so its writes + // survive it: `{ x = child.attrString("lid"); } return {id: x}` reads `lid`. + // Analyzing it against a clone discarded that and published the pre-block + // source. (A branch's block is different — it reaches here through the `if` + // arm below, which clones deliberately.) + Statement::BlockStatement(b) => collect_returns_into(&as_refs(&b.body), scope, out), Statement::IfStatement(i) => { - collect_returns(&[&i.consequent], &scope, out); + collect_returns(&[&i.consequent], scope, out); if let Some(alt) = &i.alternate { - collect_returns(&[alt], &scope, out); + collect_returns(&[alt], scope, out); } // Where the branches rejoin, a name either branch reassigned no longer has // one known value: `var x = attrString("jid"); if (c) x = attrString("lid"); // return {id: x}` reads `lid` on one legal path, and keeping the pre-branch // binding published `jid` as if it were certain. Tombstoned rather than // guessed, the same way a key bound to two different reads is refused. - tombstone_branch_writes(s, &mut scope); + tombstone_branch_writes(s, scope); } Statement::TryStatement(t) => { // The `try` body and its `catch` are alternative paths — WA wraps a @@ -864,9 +874,9 @@ fn collect_returns<'b, 'a>( // legal as the body's — and both see the pre-`try` scope, since nothing // the body wrote is guaranteed to have run when the handler is entered. let mut paths = Vec::new(); - collect_returns(&as_refs(&t.block.body), &scope, &mut paths); + collect_returns(&as_refs(&t.block.body), scope, &mut paths); if let Some(h) = &t.handler { - collect_returns(&as_refs(&h.body.body), &scope, &mut paths); + collect_returns(&as_refs(&h.body.body), scope, &mut paths); } // What the finalizer does to the earlier shapes is a control-flow // question, not a "did it yield anything" one. Emptiness gets both ends @@ -894,7 +904,7 @@ fn collect_returns<'b, 'a>( } out.extend(finalizer); // A `try` body may have run in part; nothing it wrote is certain after it. - tombstone_branch_writes(s, &mut scope); + tombstone_branch_writes(s, scope); } // A nested `switch` inside an arm (or a helper body) is how THAT arm picks // its shape — `case LINK: switch (linkType) { case "parent": return {…}; @@ -904,9 +914,9 @@ fn collect_returns<'b, 'a>( // returns. Statement::SwitchStatement(sw) => { for c in &sw.cases { - collect_returns(&as_refs(&c.consequent), &scope, out); + collect_returns(&as_refs(&c.consequent), scope, out); } - tombstone_branch_writes(s, &mut scope); + tombstone_branch_writes(s, scope); } // A loop body is a conditional path like any other: `while (c) { return A } // return B` legally produces both, and falling into the catch-all collected @@ -918,12 +928,18 @@ fn collect_returns<'b, 'a>( | Statement::WhileStatement(_) | Statement::DoWhileStatement(_) => { if let Some(body) = loop_body(s) { - collect_returns(&[body], &scope, out); + collect_returns(&[body], scope, out); } - tombstone_branch_writes(s, &mut scope); + tombstone_branch_writes(s, scope); } _ => {} } + // Nothing after a statement that leaves on every path runs. This covers the bare + // `return` and, equally, `if (c) return A; else return B;` — whose trailing + // `return C` is unreachable yet was emitted as a third legal action. + if stmt_exits(s, false) { + return; + } } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 8111509..801a6fc 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1297,6 +1297,15 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.PARAM: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, reason:t.hasAttr("reason")?norm(t.attrString("reason")):null}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.BLK: { + var bx = t.attrString("jid"); + { bx = t.attrString("lid"); } + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:bx}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.EXH: + if (t.hasChild("q")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; + else return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, c:t.attrString("c")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1660,3 +1669,40 @@ fn a_helper_that_only_normalises_its_argument_keeps_the_wire_read() { assert_eq!(f.wire_name, "reason"); assert!(!f.required, "it sits behind a presence guard"); } + +#[test] +fn a_bare_block_hands_its_writes_back() { + // A bare block always runs and `var` is function-scoped, so + // `var x = a("jid"); { x = a("lid"); } return {id: x}` reads `lid`. Analyzing the + // block against a clone discarded the write and published the pre-block source. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "blk"); + let f = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the id field"); + assert_eq!( + f.wire_name, "lid", + "the block's write is what reaches the return" + ); +} + +#[test] +fn nothing_after_an_exhaustive_if_is_reachable() { + // `if (c) return A; else return B; return C;` — C never runs. The direct-return stop + // did not cover a statement that exits through both of its branches. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let mut got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "exh") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + got.sort_unstable(); + assert_eq!( + got, + ["first", "second"], + "the trailing return is dead: {got:?}" + ); +} diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 7e4abe3..f24a4d6 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -894,7 +894,16 @@ impl<'a> Visit<'a> for ModuleScopeBuilder<'_> { // `var name = { key: val, … }` — an enum value map — or the same object // wrapped in a one-argument factory, which is how WA declares a // module-local enum: `var u = n("$InternalEnum")({Image: "image", …})`. + // Only a factory-body-level (depth 1) binding is module-scope, the same + // gate the helper branch above uses. Recording at ANY depth let two + // parsers reusing one minified name share a table: the second `u.members()` + // resolved to the first parser's values and published a closed `enumKeys` + // set that is simply wrong — an invented constraint, worse than a lost one. _ => { + if self.fn_depth != 1 { + walk::walk_variable_declarator(self, d); + return; + } let obj = wa_oxc::as_object(init).or_else(|| { wa_oxc::as_call(init) .filter(|c| c.arguments.len() == 1) From e2e01b4e3482c1b1c89047ee21b50d5f2cddaf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 10:04:21 -0300 Subject: [PATCH 30/37] notif: keep field and constant types in the generated action table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 25 — seven findings. Two are in the table added last round, and both contradict a claim this change makes in its own README. `field_list` emitted names, requiredness and a content flag, and dropped the field TYPE. The committed artifact carries 24 `user_jid`, 7 `lid_user_jid`, 13 `group_jid` and 18 integers across the action arms — so the reference consumer had to treat a PN user JID and a LID user JID as the same untyped string, which the README calls a protocol-safety-critical distinction that must never collapse. A generated `NotifFieldType` mirrors the IR vocabulary, with an exhaustive match so a new IR variant fails to compile the generator rather than silently widening an enum consumers match on. Constants were stringified into `(&str, &str)`, so `false` and the string `"false"`, `0` and `"0"`, became indistinguishable in exactly the table whose purpose is to record what the arm stamps. They are a typed `NotifConstValue` now. `primary.is_empty()` was doing duty as "no success variant selected yet" — the same emptiness-as-flag mistake the `finally` handling made two rounds ago. A fieldless first success variant left it true, so a later one overwrote the primary shape and `ParsedResponse.fields` contradicted the documented first-success rule. And the fallback index rejected a parser for having no fields at all, though `makeResult({})` guarded by `type="result"` is a confirmation whose assertions are the whole contract; a parser carrying fields still wins, so this only adds a fallback. The action-union switch is now identified by what it dispatches ON — a mapped child's tag (`var a = t.tag(); switch (a)`) — instead of by having the most arms. A module holding an unrelated const-keyed switch with more returning cases would otherwise have published its constants as wire tags. A decoy fixture pins it. Also: nested switch cases are analyzed with their fall-through suffix, like the outer one already was; and the README's migration section said three changes when `ContentType` gaining `integer` makes four. 553 tests, 12/12 schemas, determinism check clean. --- README.md | 3 +- crates/wa-codegen/src/notif_export.rs | 98 ++++++++++++++++++++++++--- crates/wa-notif/src/actions.rs | 58 +++++++++++++++- crates/wa-notif/src/tests.rs | 88 +++++++++++++++++++++++- crates/wa-scan/src/response_index.rs | 22 +++++- 5 files changed, 254 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e59e9f5..3f0889a 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,12 @@ Field shapes tell you how to *read* a stanza. They are not enough to *produce* o - **The accessor's decoded type**, kept faithful: every `attr*` / `content*` spelling WA's parsers use is classified (a `maybeAttrX` derives from `attrX`, so a flavour cannot be covered for one spelling and missed for the other), and the JID flavours stay distinct — a PN user JID and a LID user JID are different identities for the same person and must never collapse into one `string`. - **Notification action unions** (`notifications[].actions`) — the payload inside the envelope. The `wireTag → actionType` mapping is **many-to-one** (`not_ephemeral` normalises into `ephemeral` with `duration: 0`, so branching on `not_ephemeral` is dead code) and field names are rebound (the disappearing-message timer arrives in `expiration`, but the action field is `duration`). Neither is derivable from the wire. -Note the contract version: this raised `schemaVersion` to **2.0.0**, and it is a real major bump rather than a cautious one. Three changes need action from a 1.x consumer: +Note the contract version: this raised `schemaVersion` to **2.0.0**, and it is a real major bump rather than a cautious one. Four changes need action from a 1.x consumer: - `AssertionKind` gained a `reference` variant, widening the value space of an existing field — a closed-enum consumer rejects the document rather than ignoring it (validating the current `iq/index.json` against the 1.0 schema fails 579 times). - `ResponseVariantKind` gained `client_error` and `server_error`; a variant that was `error` may now be either. Match on all three, or use the `is_error()` grouping. - A response variant's `errorCodes` / `errorTexts` / `errorCodeMin` / `errorCodeMax` / `errorClass` are **gone**, replaced by `errorArms` (+ `errorEnvelope`). The flat lists were removed rather than kept alongside because they were unsound: two independent lists cannot say which code goes with which text, and 117 variants admitted combinations the parser rejects. +- `ContentType` gained `integer`, the same closed-enum widening as the first item: a `` whose body is a number used to be reported as `string`. Live in the response children that read a big-endian integer content (`contentUint`). Anything the extractor sees but cannot resolve structurally is counted under `manifest.diagnostics.iq.dropsByReason` rather than omitted, so "no constraint here" and "a constraint we failed to extract" never look alike. `manifest.diagnostics.iq.constraints` and `diagnostics.notif.actions` are floor-guarded: a WA refactor that hides one of these constructs fails the update instead of silently emptying a field. diff --git a/crates/wa-codegen/src/notif_export.rs b/crates/wa-codegen/src/notif_export.rs index 1beb0b9..bae35c8 100644 --- a/crates/wa-codegen/src/notif_export.rs +++ b/crates/wa-codegen/src/notif_export.rs @@ -267,6 +267,60 @@ fn opt_str(v: &Option) -> String { } } +/// The `NotifFieldType` variants, in IR order. +/// +/// Paired with [`field_type_variant`]'s exhaustive match: adding a variant to +/// [`wa_ir::ParsedFieldType`] fails to compile the generator rather than silently +/// widening a generated enum that a consumer has already matched on. +const FIELD_TYPE_VARIANTS: &[&str] = &[ + "String", + "Integer", + "Timestamp", + "TimestampMillis", + "Enum", + "Bytes", + "Jid", + "UserJid", + "LidUserJid", + "DeviceJid", + "LidDeviceJid", + "GroupJid", + "NewsletterJid", + "CallJid", + "BroadcastJid", + "StatusJid", + "JidTyped", + "Bool", + "Union", +]; + +/// The generated variant name for an IR field type. Exhaustive on purpose; see +/// [`FIELD_TYPE_VARIANTS`]. +fn field_type_variant(t: wa_ir::ParsedFieldType) -> &'static str { + use wa_ir::ParsedFieldType as T; + match t { + T::String => "String", + T::Integer => "Integer", + T::Timestamp => "Timestamp", + T::TimestampMillis => "TimestampMillis", + T::Enum => "Enum", + T::Bytes => "Bytes", + T::Jid => "Jid", + T::UserJid => "UserJid", + T::LidUserJid => "LidUserJid", + T::DeviceJid => "DeviceJid", + T::LidDeviceJid => "LidDeviceJid", + T::GroupJid => "GroupJid", + T::NewsletterJid => "NewsletterJid", + T::CallJid => "CallJid", + T::BroadcastJid => "BroadcastJid", + T::StatusJid => "StatusJid", + T::JidTyped => "JidTyped", + T::Bool => "Bool", + T::Union => "Union", + } +} + /// The payload action unions (`notifications[].actions`) as a const table. /// /// The envelope structs above describe what wraps the payload; this is the layer INSIDE @@ -282,6 +336,17 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { return String::new(); } let mut l = String::new(); + l.push_str( + "/// How an action field decodes. Mirrors the IR's own vocabulary rather than\n\ + /// collapsing to a Rust type, because the JID flavours must stay distinct: a PN\n\ + /// user JID and a LID user JID are different identities for the same person.\n", + ); + l.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n"); + l.push_str("pub enum NotifFieldType {\n"); + for v in FIELD_TYPE_VARIANTS { + l.push_str(&format!(" {v},\n")); + } + l.push_str("}\n\n"); l.push_str("/// One field an action arm reads off its child element.\n"); l.push_str("#[derive(Debug, Clone, Copy)]\n"); l.push_str("pub struct NotifActionField {\n"); @@ -289,11 +354,23 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { l.push_str(" pub name: &'static str,\n"); l.push_str(" /// The wire attribute (or child tag, for a content read) it comes from.\n"); l.push_str(" pub wire_name: &'static str,\n"); + l.push_str(" /// How the value decodes.\n"); + l.push_str(" pub field_type: NotifFieldType,\n"); l.push_str(" /// Whether the arm reads it unconditionally.\n"); l.push_str(" pub required: bool,\n"); l.push_str(" /// The element body is read instead of an attribute.\n"); l.push_str(" pub content: bool,\n"); l.push_str("}\n\n"); + l.push_str( + "/// A value an arm stamps rather than reading. Typed, because `false` and the\n\ + /// string `\"false\"` are different values and a stringified table cannot say which.\n", + ); + l.push_str("#[derive(Debug, Clone, Copy)]\n"); + l.push_str("pub enum NotifConstValue {\n"); + l.push_str(" Bool(bool),\n"); + l.push_str(" Int(i64),\n"); + l.push_str(" Str(&'static str),\n"); + l.push_str("}\n\n"); l.push_str("/// A repeated sub-element an arm maps over (`participants`).\n"); l.push_str("#[derive(Debug, Clone, Copy)]\n"); l.push_str("pub struct NotifActionChild {\n"); @@ -317,7 +394,7 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { " /// Fields the arm stamps to a constant rather than reading — the\n\ \x20 /// normalization that is invisible from the wire alone.\n", ); - l.push_str(" pub constants: &'static [(&'static str, &'static str)],\n"); + l.push_str(" pub constants: &'static [(&'static str, NotifConstValue)],\n"); l.push_str(" pub children: &'static [NotifActionChild],\n"); l.push_str("}\n\n"); @@ -326,9 +403,10 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { .iter() .map(|f| { format!( - "NotifActionField {{ name: {}, wire_name: {}, required: {}, content: {} }}", + "NotifActionField {{ name: {}, wire_name: {}, field_type: NotifFieldType::{}, required: {}, content: {} }}", rust_lit(&f.name), rust_lit(&f.wire_name), + field_type_variant(f.field_type), f.required, f.content ) @@ -346,11 +424,13 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { .iter() .map(|c| { let v = match &c.value { - wa_ir::NotifConstValue::Bool(b) => b.to_string(), - wa_ir::NotifConstValue::Int(i) => i.to_string(), - wa_ir::NotifConstValue::Str(s) => s.clone(), + wa_ir::NotifConstValue::Bool(b) => format!("NotifConstValue::Bool({b})"), + wa_ir::NotifConstValue::Int(i) => format!("NotifConstValue::Int({i})"), + wa_ir::NotifConstValue::Str(s) => { + format!("NotifConstValue::Str({})", rust_lit(s)) + } }; - format!("({}, {})", rust_lit(&c.name), rust_lit(&v)) + format!("({}, {v})", rust_lit(&c.name)) }) .collect(); let children: Vec = a @@ -422,9 +502,11 @@ mod tests { && src.contains(r#"action_type: Some("ephemeral")"#), "the many-to-one mapping survives:\n{src}" ); + // Typed, not stringified: `false` and the string `"false"` are different values, + // and a `(&str, &str)` table cannot say which one the arm stamps. assert!( - src.contains(r#"("duration", "0")"#), - "and the constant the arm stamps:\n{src}" + src.contains(r#"("duration", NotifConstValue::Int(0))"#), + "and the constant the arm stamps, with its type:\n{src}" ); syn::parse_file(&src).expect("generated notif.rs is valid Rust"); } diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index f73cca7..3e1cd29 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -374,6 +374,7 @@ pub(crate) fn extract_actions( let mut finder = SwitchFinder { ctx: &ctx, best: None, + tag_locals: Default::default(), }; finder.visit_program(&ret.program); finder.best.filter(|v| !v.is_empty()) @@ -426,10 +427,52 @@ struct SwitchFinder<'c, 'a> { /// The richest action union seen so far (a module can hold more than one /// const-keyed switch; the payload union is the one with the most arms). best: Option>, + /// Locals bound to a child's TAG in the function currently being walked + /// (`var a = t.tag()`), so a switch can be identified by what it dispatches ON + /// rather than by how many arms it happens to have. + tag_locals: std::collections::HashSet, +} + +/// Whether `e` reads a node's tag — `t.tag()` or `t.tag`. +fn is_tag_read(e: &Expression) -> bool { + if let Some(call) = as_call(e) { + return callee_method(call) == Some("tag") && call.arguments.is_empty(); + } + as_member(e).is_some_and(|(_, name)| name == "tag") } impl<'a> Visit<'a> for SwitchFinder<'_, 'a> { + /// Record this function's `var x = child.tag()` bindings before walking its body, + /// so a switch inside it can be checked against them. + fn visit_function_body(&mut self, body: &oxc_ast::ast::FunctionBody<'a>) { + let outer = std::mem::take(&mut self.tag_locals); + for st in &body.statements { + if let Statement::VariableDeclaration(d) = st { + for decl in &d.declarations { + if let (Some(n), Some(init)) = + (decl.id.get_identifier_name(), decl.init.as_ref()) + && is_tag_read(init) + { + self.tag_locals.insert(n.as_str().to_string()); + } + } + } + } + walk::walk_function_body(self, body); + self.tag_locals = outer; + } + fn visit_switch_statement(&mut self, switch: &SwitchStatement<'a>) { + // Identified by what it dispatches ON, not by size. The payload union switches on + // a mapped child's TAG (`var a = t.tag(); switch (a)`); an unrelated const-keyed + // switch in the same module — with more returning arms — would otherwise win the + // "most arms" tie-break and publish non-wire constants as `wireTag`s. + let on_tag = is_tag_read(&switch.discriminant) + || as_identifier(&switch.discriminant).is_some_and(|n| self.tag_locals.contains(n)); + if !on_tag { + walk::walk_switch_statement(self, switch); + return; + } let actions = extract_switch(switch, self.ctx); // A const-keyed switch whose arms only cause side effects is not a payload action // union — it is an ordinary child dispatch, already described by the @@ -913,8 +956,19 @@ fn collect_returns_into<'b, 'a>( // which is what keeps the top-level child-tag dispatch out of a helper's // returns. Statement::SwitchStatement(sw) => { - for c in &sw.cases { - collect_returns(&as_refs(&c.consequent), scope, out); + // Each case is analyzed with its own FALL-THROUGH SUFFIX, the same rule + // the outer action switch follows: `case "a": x = attrString("jid"); + // case "b": return {id: x}` runs the assignment on the `a` path, and + // analyzing the consequents independently lost `id` there. + for i in 0..sw.cases.len() { + let mut run: Vec<&Statement> = Vec::new(); + for c in &sw.cases[i..] { + run.extend(c.consequent.iter()); + if list_exits(&c.consequent, true) { + break; + } + } + collect_returns(&run, scope, out); } tombstone_branch_writes(s, scope); } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 801a6fc..8eef4c3 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1306,6 +1306,13 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr if (t.hasChild("q")) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; else return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, c:t.attrString("c")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NFT: { + var nx; + switch (t.attrString("k")) { + case "a": nx = t.attrString("jid"); + default: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:nx}; + } + } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1706,3 +1713,82 @@ fn nothing_after_an_exhaustive_if_is_reachable() { "the trailing return is dead: {got:?}" ); } + +#[test] +fn a_nested_case_carries_its_setup_to_the_case_it_falls_into() { + // `case "a": nx = t.attrString("jid"); default: return {id: nx}` — the `a` path runs + // the assignment and falls into the default's return. Analyzing each consequent + // against the same incoming scope lost `id` on that path. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "nft"); + assert!( + a.fields + .iter() + .any(|f| f.name == "id" && f.wire_name == "jid"), + "the fall-through setup reaches the return: {:?}", + a.fields + ); +} + +/// A handler whose payload union is SMALLER than an unrelated const-keyed switch beside +/// it. Picking by arm count would publish the wrong one. +const DECOY_SWITCH_BUNDLE: &str = r#" +__d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g,r,d,o,e,i,l){ + l.handle = function(){ return (function*(e,t){ + var n = e.attrs; + switch (e.tag) { + case "notification": + switch (n.type) { + case "w:gp2": return yield r("WAWebHandleGroupNotification")(e); + } + } + }); }; +}, 1); +__d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ + l.GROUP_NOTIFICATION_TAG=Object.freeze({REAL:"real"}); + l.OTHER=Object.freeze({A:"a",B:"b",C:"c",D:"d"}); +}), 1); +__d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ + l.GROUP_ACTIONS=Object.freeze({FIRST:"first"}); +}), 1); +__d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ + function h(e){ + var mode = e.attrString("mode"); + switch (mode) { + case o("WAWebHandleGroupNotificationConst").OTHER.A: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, x:e.attrString("x")}; + case o("WAWebHandleGroupNotificationConst").OTHER.B: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, y:e.attrString("y")}; + case o("WAWebHandleGroupNotificationConst").OTHER.C: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, z:e.attrString("z")}; + case o("WAWebHandleGroupNotificationConst").OTHER.D: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, w:e.attrString("w")}; + } + } + function top(e){ + var x=e.mapChildren(function(t){ + var tg = t.tag(); + switch (tg) { + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.REAL: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, real:t.attrString("real")}; + } + }); + return {actions:x}; + } + l.handleGroupNotification=top; +}), 1); +"#; + +#[test] +fn the_action_union_is_the_switch_on_a_child_tag_not_the_biggest_one() { + // A module can hold several const-keyed switches. Choosing by arm count let an + // unrelated one — four returning arms against the union's one — publish its + // constants as wire tags. The union is identified by dispatching on a child's TAG. + let ir = extract_notif(DECOY_SWITCH_BUNDLE, "2.3000.test"); + let tags: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .map(|a| a.wire_tag.as_str()) + .collect(); + assert_eq!( + tags, + ["real"], + "only the child-tag switch is the union: {tags:?}" + ); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 0e61b68..0896a52 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -81,10 +81,20 @@ impl ResponseIndex { { continue; } - if let Some(pr) = analyze_module_exports(slice, &resolver) + // A parser with no fields is still a response: `makeResult({})` guarded by + // `type="result"` is a confirmation, and its assertions are the whole + // contract. Requiring a payload dropped those to a degraded `unknown`. + let exports: Vec = analyze_module_exports(slice, &resolver) .into_iter() - .find(|(_, pr)| !pr.fields.is_empty()) .map(|(_, pr)| pr) + .collect(); + // A parser carrying fields still wins — this only adds a fallback, so which + // export is chosen does not change for any module that has a payload. + if let Some(pr) = exports + .iter() + .find(|pr| !pr.fields.is_empty()) + .or_else(|| exports.first()) + .cloned() { // The name gate above is a cheap pre-filter; this is the decision. WA's // names contradict the wire discriminator often enough that @@ -135,6 +145,7 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd } let mut variants = Vec::new(); let mut primary: Vec = Vec::new(); + let mut primary_selected = false; for (tag, module, func) in variant_refs { // Resolve the exact parser the RPC calls (`o(module).`); a // `ResponseSuccess` module exports several `parse…` fns, so match by name. @@ -144,8 +155,13 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd _ => Vec::new(), }; let kind = variant_kind(&tag, &resolver.assertions(&module, &func)); - if kind == ResponseVariantKind::Success && primary.is_empty() { + // Selection state, not payload emptiness. A first success variant with NO + // fields (a `makeResult({})` confirmation) left `primary` empty, so a later + // success variant overwrote it and `ParsedResponse.fields` contradicted the + // documented first-success rule. + if kind == ResponseVariantKind::Success && !primary_selected { primary = fields.clone(); + primary_selected = true; } // The variant's same-node discriminators (e.g. `type:"result"` / `type:"error"`), // recovered separately since the JS keeps them as parser asserts, not fields. From a84190892b0c2280801fe53df9b7b1939002ff1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 10:19:54 -0300 Subject: [PATCH 31/37] scan: sort the fallback candidates, and fix three scope rules the block fix exposed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 26 — four findings. One is a reproducibility hazard; the other three are consequences of the block-scope propagation added two rounds ago. `ResponseIndex::in_slices` was built by iterating a `HashMap`, and `resolve_for_request_op` returns the FIRST matching candidate. An operation with two success-like `WASmaxInResponse*` modules would therefore attach a different response parser — and commit different IR — from one run to the next. Byte reproducibility is this repo's contract, so nothing feeding the artifact may depend on hash order; the candidates are sorted by module name now. No op has two candidates today, which is the only reason the determinism gate never flaked on it. The other three, all in the action reader: * the reassignment rule required the new value to resolve to a wire ACCESSOR, so the minifier's `var x; x = {actionType: A, id: attr("jid")}; return x` — a fully static shape — lost its binding and published an empty action. The name now points at whatever it holds, which serves the "never report an overwritten value" rule better than forgetting it: an uninterpretable value simply does not resolve into a field. * a `do…while` body runs at least once, so its writes are definite. Grouping it with the zero-or-more loop forms tombstoned them. * propagating a bare block's scope (round 24) leaked its `let`/`const` too, so an inner `let x` shadowed the outer `x` for the rest of the function when the runtime reads the outer one. `var` still escapes; block-scoped names are restored to what they shadowed. 556 tests, 12/12 schemas, determinism check clean. --- crates/wa-notif/src/actions.rs | 59 +++++++++++++++++++++---- crates/wa-notif/src/tests.rs | 66 +++++++++++++++++++++++++++- crates/wa-scan/src/response_index.rs | 9 +++- 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 3e1cd29..9610ef8 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -877,12 +877,14 @@ fn collect_returns_into<'b, 'a>( flatten_sequence(&e.expression, &mut ops); for a in ops { if let Some(name) = a.left.get_identifier_name() { - let reads_wire = find_accessor(strip_guard(&a.right), scope).is_some(); - if reads_wire { - scope.insert(name, &a.right); - } else { - scope.remove(name); - } + // The binding now holds the new right-hand side, whatever it is. + // Requiring it to resolve to a wire accessor dropped the minifier's + // `var x; x = {actionType: A, id: attr("jid")}; return x` — a fully + // static shape — and published an empty action. Pointing the name at + // what it now holds satisfies the "never report an overwritten + // value" rule better than forgetting it: if the new value is not + // interpretable, the field simply does not resolve. + scope.insert(name, &a.right); } } } @@ -898,7 +900,22 @@ fn collect_returns_into<'b, 'a>( // Analyzing it against a clone discarded that and published the pre-block // source. (A branch's block is different — it reaches here through the `if` // arm below, which clones deliberately.) - Statement::BlockStatement(b) => collect_returns_into(&as_refs(&b.body), scope, out), + Statement::BlockStatement(b) => { + // `var` writes escape a bare block (function scope); `let`/`const` do not. + // Propagating everything let an inner `let x` shadow the outer `x` for the + // rest of the function — the runtime reads the OUTER value after the block. + let shadowed: Vec<(&str, Option<&'b Expression<'a>>)> = block_scoped_names(&b.body) + .into_iter() + .map(|n| (n, scope.get(n).copied())) + .collect(); + collect_returns_into(&as_refs(&b.body), scope, out); + for (name, prior) in shadowed { + match prior { + Some(e) => scope.insert(name, e), + None => scope.remove(name), + }; + } + } Statement::IfStatement(i) => { collect_returns(&[&i.consequent], scope, out); if let Some(alt) = &i.alternate { @@ -976,11 +993,15 @@ fn collect_returns_into<'b, 'a>( // return B` legally produces both, and falling into the catch-all collected // only B. Its writes are tombstoned on exit for the same reason a branch's // are — the body may have run any number of times, including zero. + // A `do…while` body runs AT LEAST once, so its writes are definite — the + // ordinary loop merge tombstoned them and lost every field they bound. + Statement::DoWhileStatement(d) => { + collect_returns_into(&[&d.body], scope, out); + } Statement::ForStatement(_) | Statement::ForInStatement(_) | Statement::ForOfStatement(_) - | Statement::WhileStatement(_) - | Statement::DoWhileStatement(_) => { + | Statement::WhileStatement(_) => { if let Some(body) = loop_body(s) { collect_returns(&[body], scope, out); } @@ -997,6 +1018,26 @@ fn collect_returns_into<'b, 'a>( } } +/// The names a statement list declares with `let`/`const` — bindings that die with their +/// block, unlike `var`. +fn block_scoped_names<'b, 'a>(stmts: &'b [Statement<'a>]) -> Vec<&'b str> { + use oxc_ast::ast::VariableDeclarationKind as K; + let mut out = Vec::new(); + for s in stmts { + if let Statement::VariableDeclaration(d) = s + && matches!(d.kind, K::Let | K::Const) + { + out.extend( + d.declarations + .iter() + .filter_map(|x| x.id.get_identifier_name()) + .map(|n| n.as_str()), + ); + } + } + out +} + /// The body statement of any loop form, so one arm can handle them all. fn loop_body<'b, 'a>(s: &'b Statement<'a>) -> Option<&'b Statement<'a>> { match s { diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 8eef4c3..d834e10 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1313,6 +1313,21 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr default: return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:nx}; } } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.LATE: { + var lr; + lr = {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:t.attrString("jid")}; + return lr; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DOW: { + var dw; + do { dw = t.attrString("lid"); } while (t.hasChild("again")); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:dw}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SHADOW: { + var sh = t.attrString("jid"); + { let sh = t.attrString("lid"); } + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, id:sh}; + } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1792,3 +1807,52 @@ fn the_action_union_is_the_switch_on_a_child_tag_not_the_biggest_one() { "only the child-tag switch is the union: {tags:?}" ); } + +#[test] +fn a_shape_assigned_after_its_declaration_is_still_static() { + // `var lr; lr = {actionType: A, id: attr("jid")}; return lr` is fully static, but the + // reassignment rule required the right-hand side to resolve to a wire ACCESSOR and + // dropped the binding for an object literal, publishing an empty action. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "late"); + assert_eq!(a.action_type.as_deref(), Some("first")); + assert!( + a.fields + .iter() + .any(|f| f.name == "id" && f.wire_name == "jid"), + "the whole shape survives: {:?}", + a.fields + ); +} + +#[test] +fn a_do_while_body_write_is_definite() { + // A `do…while` body runs at least once, so every path reaching the return has run it. + // Grouping it with the zero-or-more loops tombstoned the write and lost the field. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "dow"); + assert!( + a.fields + .iter() + .any(|f| f.name == "id" && f.wire_name == "lid"), + "the body's write reaches the return: {:?}", + a.fields + ); +} + +#[test] +fn a_block_scoped_shadow_does_not_escape_its_block() { + // `var sh = a("jid"); { let sh = a("lid"); } return {id: sh}` reads `jid` — `let` dies + // with its block. Propagating the block's scope wholesale (the `var` fix) leaked it. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "shadow"); + let f = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the id field"); + assert_eq!( + f.wire_name, "jid", + "the OUTER binding is what the return reads" + ); +} diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 0896a52..9dbe321 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -223,11 +223,18 @@ pub(crate) fn build_pass(defs: &[ModuleDefinition], source: &str) -> ResponseInd } // Keep the `WASmaxIn*` slices (owned) for the request-anchored fallback. - let in_slices: Vec<(String, String)> = slices + // SORTED. `slices` is a `HashMap`, so iterating it hands the fallback its candidates + // in a different order every run — and the fallback takes the first match. An op with + // two success-like `WASmaxInResponse*` modules would therefore attach a different + // response parser, and commit different IR, from one run to the next. Byte + // reproducibility is this repo's contract; nothing that feeds the artifact may depend + // on hash order. + let mut in_slices: Vec<(String, String)> = slices .iter() .filter(|(n, _)| n.starts_with("WASmaxIn")) .map(|(n, s)| (n.to_string(), s.to_string())) .collect(); + in_slices.sort_by(|a, b| a.0.cmp(&b.0)); ResponseIndex { by_x, From 2c09ff947d5cee52aec4e2c220a08566f0116faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 10:30:24 -0300 Subject: [PATCH 32/37] scan: count the two enum/marker constraints that were vanishing silently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 27 — two findings, both a silent loss, which is the one outcome this change exists to prevent. `attrEnumOrNullIfUnknown` took a fast path for a module-scope identifier table and returned unconditionally. Given anything else — `o("Mod").TABLE`, a computed set — it produced no pending link, no drop, and with no companion read no field at all, while the parser does enforce an enum there. The miss is now recorded and resolved alongside the hits: an existing companion read is retyped to an enum carrying `PendingEnum::Unresolvable`, and where no companion exists the field is synthesized, so the constraint is counted either way instead of vanishing. `literalContent` emitted a `Content` assertion only for an inline string literal. A disjunction whose marker arrives through a local constant therefore published fieldless variants with no usable discriminator while `dropsByReason` claimed nothing was lost. The unresolvable occurrence is reported now, keyed per node path like its neighbours. Both are latent against the pinned bundle — every table today is a module-scope identifier and every marker an inline literal — so `generated/` and every drop count are unchanged, and tests carry them. Also worth noting for the review record: these two arrived in the review BODY rather than as inline comments, so an unresolved-thread sweep does not see them. Both were verified by reverting the fix and watching the test fail; the first attempt at that revert edited the reason string in the test as well as the source, which made the test pass for the wrong reason. 558 tests, 12/12 schemas, determinism check clean. --- crates/wa-scan/src/response.rs | 77 +++++++++++++++++++++++++++-- crates/wa-scan/src/response_smax.rs | 56 +++++++++++++++++++-- 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index f24a4d6..9352fb4 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -425,6 +425,7 @@ fn analyze_with_scope(code: &str, param: &str, module: &ModuleScope) -> ParserRe fields: Vec::new(), child_vars: HashMap::new(), pending_enum_keys: HashMap::new(), + unresolved_enum_attrs: Default::default(), unresolved: Vec::new(), helper_depth: 0, }; @@ -453,6 +454,10 @@ struct ParserAnalyzer<'src, 'ms> { /// (the map is module-scope, so it's resolved and stashed here, then attached to the /// matching field in a post-pass — order-independent of the plain read of the attr). pending_enum_keys: HashMap>, + /// Wire attributes read by `attrEnumOrNullIfUnknown` whose allowed-value table could + /// not be named. Resolved in [`ParserAnalyzer::attach_pending_enum_keys`] alongside + /// the ones that did resolve. + unresolved_enum_attrs: std::collections::BTreeSet, /// Constraints seen on this parser but not statically resolvable, as /// `dropsByReason` keys. Parked on the produced [`ParsedResponse`] for whoever /// finishes it, since the legacy scanner has no diagnostics channel of its own. @@ -551,16 +556,26 @@ impl ParserAnalyzer<'_, '_> { && obj_is_param && let Some(wire) = arg_str(call, 0) { - if let Some(keys) = call + match call .arguments .get(1) .and_then(arg_expr) .and_then(as_identifier) .and_then(|m| self.module.maps.get(m).cloned()) { - self.pending_enum_keys - .entry(wire.to_string()) - .or_insert(keys); + Some(keys) => { + self.pending_enum_keys + .entry(wire.to_string()) + .or_insert(keys); + } + // The table is not a module-scope identifier — `o("Mod").TABLE`, or a + // computed set. The parser still enforces an enum here, so returning + // silently produced neither a link nor a drop and the new diagnostics + // reported nothing lost. Recorded so it is counted, and so a field gets + // synthesized when no companion read created one. + None => { + self.unresolved_enum_attrs.insert(wire.to_string()); + } } return; } @@ -779,6 +794,34 @@ impl ParserAnalyzer<'_, '_> { self.fields.push(f); } } + // The same two halves for a table that could NOT be named: mark the companion read + // as an enum with an unrecoverable value set, or synthesize the field, so the + // constraint is counted either way instead of vanishing. + for wire in std::mem::take(&mut self.unresolved_enum_attrs) { + match self + .fields + .iter_mut() + .find(|f| f.name == wire && f.tag.is_none()) + { + Some(f) if f.enum_keys.is_none() && f.pending_enum_ref.is_none() => { + let optional = wap::is_optional_method(&f.method); + f.method = if optional { + wap::MAYBE_ATTR_ENUM.to_string() + } else { + wap::ATTR_ENUM.to_string() + }; + f.field_type = ParsedFieldType::Enum; + f.required = !optional; + f.pending_enum_ref = Some(wa_ir::PendingEnum::Unresolvable); + } + Some(_) => {} + None => { + let mut f = mk_field(wap::MAYBE_ATTR_ENUM, &wire, ParsedFieldType::Enum, false); + f.pending_enum_ref = Some(wa_ir::PendingEnum::Unresolvable); + self.fields.push(f); + } + } + } } } @@ -964,6 +1007,7 @@ fn analyze_child_node( fields: Vec::new(), child_vars: HashMap::from([(node_param.to_string(), tag.to_string())]), pending_enum_keys: HashMap::new(), + unresolved_enum_attrs: Default::default(), unresolved: Vec::new(), helper_depth: depth, }; @@ -1517,6 +1561,31 @@ mod tests { ); } + #[test] + fn an_unnameable_enum_table_is_still_counted() { + // `attrEnumOrNullIfUnknown("type", o("Mod").TABLE)` — the table is not a + // module-scope identifier, so the fast path missed and returned, producing no + // link, no drop and (with no companion read) no field. The parser enforces an + // enum there; a silent loss is the one outcome this change exists to prevent. + let module = r#"__d("M",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var c=new(r("WADeprecatedWapParser"))("p", function(e){ + e.assertTag("receipt"); + var t=e.attrEnumOrNullIfUnknown("type", o("Other").TABLE); + return {}; + }); + }),1);"#; + let out = parse_module_wap_parsers(module); + let p = out.iter().find(|r| r.parser_name == "p").expect("parser"); + let f = p + .fields + .iter() + .find(|f| f.name == "type") + .expect("the field is synthesized rather than vanishing"); + assert_eq!(f.field_type, ParsedFieldType::Enum); + assert_eq!(f.pending_enum_ref, Some(wa_ir::PendingEnum::Unresolvable)); + assert!(f.enum_keys.is_none(), "and no invented value set"); + } + #[test] fn a_companion_read_is_retyped_when_it_gains_enum_keys() { // `attrEnumOrNullIfUnknown("type", map)` beside a plain `maybeAttrString("type")` diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 35d9b4b..59037a9 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -709,15 +709,29 @@ fn classify_call( // `all_member_add`). Not an output field (the value is fixed); record it as a // Content assertion when on the same node. "literalContent" => { - if source_path.is_none() - && let Some(value) = args.get(2).and_then(arg_expr).and_then(as_string_lit) - { - assertions.push(ResponseAssertion { + let value = args.get(2).and_then(arg_expr).and_then(as_string_lit); + match value { + Some(value) if source_path.is_none() => assertions.push(ResponseAssertion { kind: AssertionKind::Content, name: None, value: Some(value.to_string()), reference_path: None, - }); + }), + // A marker value supplied through a local constant or a computed + // expression. The disjunction still discriminates on it, so emitting no + // assertion AND no drop published fieldless variants with no usable + // discriminator while the diagnostics claimed nothing was lost. + None => resolver.drop_note_keyed( + "literalContent marker value not statically resolvable", + format!( + "{site}:{}", + source_path + .as_ref() + .map(|p| p.join("/")) + .unwrap_or_else(|| "".to_string()) + ), + ), + Some(_) => {} } Binding::None } @@ -2154,6 +2168,38 @@ mod tests { analyze_module_exports(module, &resolver) } + #[test] + fn an_unresolvable_literal_content_marker_is_counted() { + // A disjunction discriminates on `literalContent`. When the marker value arrives + // through a local constant rather than an inline string, emitting neither an + // assertion nor a drop published a fieldless variant with no usable + // discriminator while the diagnostics claimed nothing was lost. + let slices = HashMap::new(); + let resolver = Resolver::new(&slices); + let (asserts, _) = analyze_fn_source( + r#"function p(node){ + var v = MARKER; + var a = o("WASmaxParseUtils").literalContent(node, "x", v); + return o("WAResultOrError").makeResult({}); + }"#, + &LocalFns::new(), + &resolver, + &mut HashSet::new(), + ) + .expect("analyzed"); + assert!( + !asserts.iter().any(|a| a.kind == AssertionKind::Content), + "no discriminator is invented" + ); + assert_eq!( + resolver + .drop_counts() + .get("literalContent marker value not statically resolvable"), + Some(&1), + "but the loss is reported" + ); + } + #[test] fn a_content_enum_keeps_a_content_spelling() { // `contentStringEnum(node, TABLE)` reads the ELEMENT BODY. Normalizing it to From 25ad520b0512cdcd7ae74520211fd9dc7cdff668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 10:51:55 -0300 Subject: [PATCH 33/37] scan: stop two invented constraints, and pick the response root by structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 28 — five findings: four inline, one in the review body (the monitor now surfaces those, which is how the fifth was seen at all). Two published constraints that are not true, the worst outcome this change can produce: * `obj_props` silently skips a spread, a method and a computed key, so a module-local enum containing one collected its SURVIVING literals successfully and shipped them as a closed set — claiming the parser rejects whatever the skipped property contributed. A table that cannot be read whole is not a table; it is refused and counted. * `assertions_conflict` read `Some("result") != None` as a discriminator conflict. A parser that merely requires `type` to exist also accepts `type="result"`, so the two variants are not disjoint, and the separability gate would emit a first-success cascade whose earlier arm shadows the later. Both sides must now pin a literal on the same named attribute. The fieldless-parser fix from round 25 picked the wrong export when a module has both: preferring "whichever has fields" chose the payload helper over a `makeResult({})` root, losing the root's assertions — which for a confirmation ARE the contract. Selection is structural now (a child export's name extends another's at a word boundary), the same rule the incoming ack scan uses. Also: a `catch` parameter shadows the outer binding, so `var e = attrString("jid"); catch (e) { return {id: e} }` published `id` as a read of `jid`; and the generated `NotifActionField` now carries `enum_values`, since `NotifFieldType::Enum` says a value is constrained but not to what. No action field resolves an enum today, so that one is latent. 561 tests, 12/12 schemas, determinism check clean. Each fix was verified by reverting it alone and watching its test fail. The first attempt reverted all three in one script, which aborted on the first mismatched anchor and left every fix in place — the tests then passed for the wrong reason. Third time that has happened in this review; one revert per run, checked. --- crates/wa-codegen/src/notif_export.rs | 16 ++++++++-- crates/wa-codegen/src/spec.rs | 45 +++++++++++++++++++++++++-- crates/wa-notif/src/actions.rs | 13 +++++++- crates/wa-notif/src/tests.rs | 26 +++++++++++++++- crates/wa-scan/src/response.rs | 29 +++++++++++++++++ crates/wa-scan/src/response_index.rs | 27 +++++++++++----- 6 files changed, 141 insertions(+), 15 deletions(-) diff --git a/crates/wa-codegen/src/notif_export.rs b/crates/wa-codegen/src/notif_export.rs index bae35c8..862454f 100644 --- a/crates/wa-codegen/src/notif_export.rs +++ b/crates/wa-codegen/src/notif_export.rs @@ -360,6 +360,12 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { l.push_str(" pub required: bool,\n"); l.push_str(" /// The element body is read instead of an attribute.\n"); l.push_str(" pub content: bool,\n"); + l.push_str( + " /// The wire enum the handler validates this value against, when the\n\ + \x20 /// extractor could name it. A `field_type` of `Enum` says the value is\n\ + \x20 /// constrained; this says to WHAT.\n", + ); + l.push_str(" pub enum_values: &'static [&'static str],\n"); l.push_str("}\n\n"); l.push_str( "/// A value an arm stamps rather than reading. Typed, because `false` and the\n\ @@ -402,13 +408,19 @@ fn emit_action_tables(notifications: &[NotificationDef]) -> String { let items: Vec = fields .iter() .map(|f| { + let values: Vec = f + .enum_ref + .as_ref() + .map(|e| e.variants.iter().map(|v| rust_lit(&v.value)).collect()) + .unwrap_or_default(); format!( - "NotifActionField {{ name: {}, wire_name: {}, field_type: NotifFieldType::{}, required: {}, content: {} }}", + "NotifActionField {{ name: {}, wire_name: {}, field_type: NotifFieldType::{}, required: {}, content: {}, enum_values: &[{}] }}", rust_lit(&f.name), rust_lit(&f.wire_name), field_type_variant(f.field_type), f.required, - f.content + f.content, + values.join(", ") ) }) .collect(); diff --git a/crates/wa-codegen/src/spec.rs b/crates/wa-codegen/src/spec.rs index 514f031..02f3cfa 100644 --- a/crates/wa-codegen/src/spec.rs +++ b/crates/wa-codegen/src/spec.rs @@ -13,12 +13,20 @@ use wa_ir::{ /// to DIFFERENT literal values (`type:"result"` vs `type:"error"`): a response /// satisfying one fails the other's guard, so neither can shadow the other. pub(crate) fn assertions_conflict(a: &ResponseVariant, b: &ResponseVariant) -> bool { + // BOTH sides must pin a literal, and to the same NAMED attribute. `Some("result")` + // against a presence-only assertion (`value: None`) is not a conflict: a parser that + // merely requires `type` to exist also accepts `type="result"`, so the variants are + // not disjoint and the earlier arm shadows the later one in a first-success cascade. a.assertions.iter().any(|x| { x.kind == AssertionKind::Attr + && x.name.is_some() && x.value.is_some() - && b.assertions - .iter() - .any(|y| y.kind == AssertionKind::Attr && y.name == x.name && y.value != x.value) + && b.assertions.iter().any(|y| { + y.kind == AssertionKind::Attr + && y.name == x.name + && y.value.is_some() + && y.value != x.value + }) }) } @@ -767,6 +775,37 @@ mod tests { use super::*; use wa_ir::{IqRequestDef, ParsedResponse}; + fn conflict_attr(name: &str, value: Option<&str>) -> wa_ir::ResponseAssertion { + wa_ir::ResponseAssertion { + kind: AssertionKind::Attr, + name: Some(name.to_string()), + value: value.map(str::to_string), + reference_path: None, + } + } + + fn conflict_variant(assertions: Vec) -> wa_ir::ResponseVariant { + wa_ir::ResponseVariant { + assertions, + ..Default::default() + } + } + + #[test] + fn a_presence_only_assertion_does_not_conflict_with_a_pin() { + // A parser that merely requires `type` to EXIST also accepts `type="result"`, so + // the two variants are not disjoint. Treating `Some("result") != None` as a + // conflict let the separability gate emit a first-success cascade whose earlier + // arm shadows the later one. + let pinned = conflict_variant(vec![conflict_attr("type", Some("result"))]); + let present = conflict_variant(vec![conflict_attr("type", None)]); + assert!(!assertions_conflict(&pinned, &present)); + assert!(!assertions_conflict(&present, &pinned)); + // Two different pins on the same attribute still conflict. + let other = conflict_variant(vec![conflict_attr("type", Some("error"))]); + assert!(assertions_conflict(&pinned, &other)); + } + fn stanza(module: &str, exported: Option<&str>) -> IqStanzaDef { IqStanzaDef { module_name: module.into(), diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 9610ef8..dfd10ab 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -936,7 +936,18 @@ fn collect_returns_into<'b, 'a>( let mut paths = Vec::new(); collect_returns(&as_refs(&t.block.body), scope, &mut paths); if let Some(h) = &t.handler { - collect_returns(&as_refs(&h.body.body), scope, &mut paths); + // The catch PARAMETER is the caught exception, not whatever the outer + // scope bound to that name. `var e = attrString("jid"); try {…} + // catch (e) { return {id: e} }` published `id` as a read of `jid`; + // masking it makes the handler's `e` resolve to nothing, which is the + // truth. + let mut catch_scope = scope.clone(); + if let Some(param) = h.param.as_ref() + && let Some(name) = param.pattern.get_identifier_name() + { + catch_scope.remove(name.as_str()); + } + collect_returns(&as_refs(&h.body.body), &catch_scope, &mut paths); } // What the finalizer does to the earlier shapes is a control-flow // question, not a "did it yield anything" one. Emptiness gets both ends diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index d834e10..88cd375 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1328,6 +1328,11 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr { let sh = t.attrString("lid"); } return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, id:sh}; } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.CATCHP: { + var e2 = t.attrString("jid"); + try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:e2}; } + catch (e2) { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:e2}; } + } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1856,3 +1861,22 @@ fn a_block_scoped_shadow_does_not_escape_its_block() { "the OUTER binding is what the return reads" ); } + +#[test] +fn a_catch_parameter_shadows_the_outer_binding() { + // The catch parameter IS the caught exception, not whatever the outer scope bound to + // that name. Analyzing the handler against the unmodified scope published the catch + // arm's `id` as a read of `jid`. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let second: Vec<_> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "catchp" && a.action_type.as_deref() == Some("second")) + .collect(); + assert_eq!(second.len(), 1, "the catch arm is recovered"); + assert!( + second[0].fields.is_empty(), + "but its `id` resolves to the exception, not to `jid`: {:?}", + second[0].fields + ); +} diff --git a/crates/wa-scan/src/response.rs b/crates/wa-scan/src/response.rs index 9352fb4..0914f00 100644 --- a/crates/wa-scan/src/response.rs +++ b/crates/wa-scan/src/response.rs @@ -953,6 +953,12 @@ impl<'a> Visit<'a> for ModuleScopeBuilder<'_> { .and_then(|c| arg_expr(&c.arguments[0])) .and_then(wa_oxc::as_object) }); + // `obj_props` silently skips a spread, a method and a computed key, + // so an object containing one yields a SHORTER list that still + // collects successfully — publishing a closed set that claims the + // parser rejects whatever the skipped property contributed. A table + // we cannot read whole is not a table. + let obj = obj.filter(|o| o.properties.len() == wa_oxc::obj_props(o).count()); if let Some(obj) = obj { let n = name.as_str().to_string(); // Keys and values are both allowed-value sets, for *different* @@ -1771,6 +1777,29 @@ mod tests { } } + #[test] + fn a_table_with_a_spread_is_not_a_complete_table() { + // `obj_props` silently skips a spread, so the surviving literals collected + // successfully and were published as a CLOSED set — claiming the parser rejects + // whatever the spread contributed. An invented constraint, not a lost one. + let module = r#"__d("M",["WADeprecatedWapParser"],(function(t,n,r,o,a,i,l){ + var u=n("$InternalEnum")({A:"a", ...base}); + var c=new(r("WADeprecatedWapParser"))("p", function(e){ + e.assertTag("message"); + e.attrEnumValues("k", u.members()); + return {}; + }); + }),1);"#; + let out = parse_module_wap_parsers(module); + let p = out.iter().find(|r| r.parser_name == "p").expect("parser"); + let f = p.fields.iter().find(|f| f.name == "k").expect("field"); + assert!( + f.enum_keys.is_none(), + "an unreadable table is not a value set" + ); + assert_eq!(f.pending_enum_ref, Some(wa_ir::PendingEnum::Unresolvable)); + } + #[test] fn a_partially_literal_enum_table_is_refused_whole() { // `{A: "a", B: computed}` must not publish `["a"]` as the complete legal set — diff --git a/crates/wa-scan/src/response_index.rs b/crates/wa-scan/src/response_index.rs index 9dbe321..68e2496 100644 --- a/crates/wa-scan/src/response_index.rs +++ b/crates/wa-scan/src/response_index.rs @@ -84,17 +84,28 @@ impl ResponseIndex { // A parser with no fields is still a response: `makeResult({})` guarded by // `type="result"` is a confirmation, and its assertions are the whole // contract. Requiring a payload dropped those to a degraded `unknown`. - let exports: Vec = analyze_module_exports(slice, &resolver) - .into_iter() - .map(|(_, pr)| pr) - .collect(); - // A parser carrying fields still wins — this only adds a fallback, so which - // export is chosen does not change for any module that has a payload. + let exports = analyze_module_exports(slice, &resolver); + // The ROOT export, structurally: a child/helper parser's export name extends + // another export's at a word boundary (`parseFooResponseSuccess` vs + // `…SuccessPayload`), and its fields already nest in the root's tree. The + // same rule the incoming ack scan uses. + // + // Preferring "whichever has fields" picked the payload helper whenever the + // root was a fieldless confirmation — losing the root's assertions, which for + // a `makeResult({})` response ARE the whole contract. + let is_child = |name: &String| { + exports.iter().any(|(other, _)| { + other != name + && name + .strip_prefix(other.as_str()) + .is_some_and(|suffix| suffix.starts_with(char::is_uppercase)) + }) + }; if let Some(pr) = exports .iter() - .find(|pr| !pr.fields.is_empty()) + .find(|(name, _)| !is_child(name)) .or_else(|| exports.first()) - .cloned() + .map(|(_, pr)| pr.clone()) { // The name gate above is a cheap pre-filter; this is the decision. WA's // names contradict the wire discriminator often enough that From 37698d926fd7f0237b13f261272c5a3843a798ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 11:11:05 -0300 Subject: [PATCH 34/37] codegen: derive optionality from the IR, not only from the accessor name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 29 — four findings. The first is live at scale and rejects valid responses. `rust_field_type` and `emit_field_parse` derived optionality from the accessor SPELLING alone, but a smax tail conditional makes a field optional without renaming its accessor. The committed artifact carries **108** JID fields that are `attrUserJid`/`attrLidUserJid`/… with `required: false`; every one was declared a plain `Jid` and parsed with `ok_or_else("missing …")?`, so the generated parser rejected responses the IR explicitly says may omit them. `required` is now a second source of optionality, in the struct type and the parser alike (`union::field_expr` too). `pub to: Jid` → `pub to: Option`, and `missing to` disappears from the generated file. The `errorTexts` floor counted DISTINCT text strings, so one RPC losing its `bad-request` pin while another still carried it moved nothing — and the arm survives, so `errorArms` did not move either. The guard could pass while a specific RPC's accepted `(code, text)` pairing silently degraded. Keyed by `(module, variant, arm, text)` now: 38 → 577, and the doc says what the number means. `stmt_exits` had no `do…while` arm, so `do { return A } while (c); return B` published the unreachable B; and an unresolvable `contentUint` width — a hoisted constant hides the number, not the constraint — produced an unrestricted integer with no drop recorded. 562 tests, 12/12 schemas, determinism check clean. Each fix reverted alone to watch its test fail; two of the four edits in this round again failed to apply as text substitutions before being redone against the formatted source. --- crates/wa-codegen/src/emit.rs | 5 ++++- crates/wa-codegen/src/fields.rs | 8 +++++++- crates/wa-codegen/src/lib.rs | 27 ++++++++++++++++++++++----- crates/wa-codegen/src/union.rs | 2 +- crates/wa-notif/src/actions.rs | 4 ++++ crates/wa-notif/src/tests.rs | 20 +++++++++++++++++++- crates/wa-scan/src/response_smax.rs | 18 +++++++++++++++--- crates/whatspec/src/main.rs | 24 ++++++++++++++++++++++-- generated/manifest.json | 2 +- 9 files changed, 95 insertions(+), 15 deletions(-) diff --git a/crates/wa-codegen/src/emit.rs b/crates/wa-codegen/src/emit.rs index f5f1786..5055cba 100644 --- a/crates/wa-codegen/src/emit.rs +++ b/crates/wa-codegen/src/emit.rs @@ -51,7 +51,10 @@ pub(crate) fn emit_field_parse(f: &ParsedField, node_var: &str, indent: &str) -> return Vec::new(); } - let optional = wap::is_optional_method(method); + // See `rust_field_type`: the accessor spelling and the IR's `required` are both + // sources of optionality, and the two must agree or the initializer will not match the + // declared field type. + let optional = wap::is_optional_method(method) || !f.required; match wap::method_field_type(method) { ParsedFieldType::Integer if optional => vec![format!( "{indent}let {name} = {node_var}.get_attr({flit}).and_then(|v| v.as_str().parse().ok());" diff --git a/crates/wa-codegen/src/fields.rs b/crates/wa-codegen/src/fields.rs index d4a9c7e..72137c7 100644 --- a/crates/wa-codegen/src/fields.rs +++ b/crates/wa-codegen/src/fields.rs @@ -89,7 +89,13 @@ pub(crate) fn rust_field_type(field: &ParsedField) -> &'static str { // String / Enum / (Bool/Union handled elsewhere) materialize as String. _ => "String", }; - if wap::is_optional_method(&field.method) { + // Optionality has TWO sources. The accessor spelling is one; the IR's own `required` + // is the other, and it is the only one that sees a smax tail conditional making a + // field optional without changing its accessor. 108 JID fields in the committed + // artifact are `attrUserJid` with `required: false`, and deriving from the name alone + // declared them `Jid` and emitted `ok_or_else("missing …")` — rejecting responses the + // IR explicitly says may omit them. + if wap::is_optional_method(&field.method) || !field.required { match base { "u64" => "Option", "Vec" => "Option>", diff --git a/crates/wa-codegen/src/lib.rs b/crates/wa-codegen/src/lib.rs index 44ba592..81cebb0 100644 --- a/crates/wa-codegen/src/lib.rs +++ b/crates/wa-codegen/src/lib.rs @@ -399,11 +399,20 @@ mod tests { response: ParsedResponse { parser_name: "p".into(), assertions: vec![], - fields: vec![parsed( - "maybeAttrPhoneUserJid", - "participant_pn", - ParsedFieldType::UserJid, - )], + fields: vec![ + parsed( + "maybeAttrPhoneUserJid", + "participant_pn", + ParsedFieldType::UserJid, + ), + // Optional by the IR, NOT by the accessor spelling: a smax tail + // conditional makes a field optional without renaming its + // accessor, and 108 such JID fields are live in the artifact. + ParsedField { + required: false, + ..parsed("attrUserJid", "to", ParsedFieldType::UserJid) + }, + ], ..Default::default() }, }], @@ -418,6 +427,14 @@ mod tests { !c.contains("missing participant_pn"), "and never required in the parser:\n{c}" ); + assert!( + c.contains("pub to: Option,"), + "`required: false` alone is enough to make it optional:\n{c}" + ); + assert!( + !c.contains("missing to"), + "so the parser never demands it:\n{c}" + ); assert!( c.contains(".and_then(|v| v.to_jid());"), "parsed as an optional Jid:\n{c}" diff --git a/crates/wa-codegen/src/union.rs b/crates/wa-codegen/src/union.rs index f17067b..1577f85 100644 --- a/crates/wa-codegen/src/union.rs +++ b/crates/wa-codegen/src/union.rs @@ -712,7 +712,7 @@ fn field_expr(f: &ParsedField, node_var: &str) -> String { } let wire = f.wire_name.as_deref().unwrap_or(&f.name); let flit = rust_lit(wire); - let optional = wap::is_optional_method(method); + let optional = wap::is_optional_method(method) || !f.required; match wap::method_field_type(method) { ParsedFieldType::Integer if optional => { format!("{node_var}.get_attr({flit}).and_then(|v| v.as_str().parse().ok())") diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index dfd10ab..2ec3f0a 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -668,6 +668,10 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { .as_ref() .is_none_or(|h| list_exits(&h.body.body, nested))) } + // A `do…while` body runs at least once, so if it exits on every path the loop + // does. `do { return A } while (c); return B` was collecting A and then walking + // on to publish the unreachable B. + Statement::DoWhileStatement(d) => stmt_exits(&d.body, nested), Statement::SwitchStatement(sw) => { sw.cases.iter().any(|c| c.test.is_none()) && (0..sw.cases.len()).all(|i| { diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 88cd375..8a736d3 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp",DOWX:"dowx"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1333,6 +1333,9 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr try { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:e2}; } catch (e2) { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:e2}; } } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DOWX: + do { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } while (t.hasChild("again")); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1880,3 +1883,18 @@ fn a_catch_parameter_shadows_the_outer_binding() { second[0].fields ); } + +#[test] +fn nothing_after_an_exiting_do_while_is_reachable() { + // `do { return A } while (c); return B` — the body runs at least once and returns, so + // B is dead. `stmt_exits` had no `do…while` arm, so traversal walked on and published + // it as a second legal action. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "dowx") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!(got, ["first"], "only the reachable shape: {got:?}"); +} diff --git a/crates/wa-scan/src/response_smax.rs b/crates/wa-scan/src/response_smax.rs index 59037a9..4f6f12a 100644 --- a/crates/wa-scan/src/response_smax.rs +++ b/crates/wa-scan/src/response_smax.rs @@ -836,7 +836,7 @@ fn classify_call( match inner.and_then(normalize_accessor) { Some((m, ft, bl)) => { let (field_type, int_range) = int_range_and_type(inner, args, ft); - let (cbl, byte_range) = content_byte_length(inner, args); + let (cbl, byte_range) = content_byte_length(inner, args, resolver, site); let enum_ref = enum_arg_ref(inner, args, resolver, site, source_path.as_deref()); Binding::Field { @@ -869,7 +869,7 @@ fn classify_call( other => match normalize_accessor(other) { Some((m, ft, bl)) => { let (field_type, int_range) = int_range_and_type(Some(other), args, ft); - let (cbl, byte_range) = content_byte_length(Some(other), args); + let (cbl, byte_range) = content_byte_length(Some(other), args, resolver, site); let enum_ref = enum_arg_ref(Some(other), args, resolver, site, source_path.as_deref()); Binding::Field { @@ -1230,6 +1230,8 @@ fn int_range_and_type( fn content_byte_length( accessor: Option<&str>, args: &[Argument], + resolver: &Resolver, + site: &str, ) -> (Option, Option<(u32, u32)>) { // `contentUint(N)` reads N big-endian bytes, so N is a fixed length like // `contentBytes(N)`'s — not a bound. @@ -1238,7 +1240,17 @@ fn content_byte_length( .iter() .filter_map(|a| arg_expr(a).and_then(as_int)) .next(); - return (n.and_then(|n| u32::try_from(n).ok()), None); + let width = n.and_then(|n| u32::try_from(n).ok()); + if width.is_none() { + // Recognizing the accessor PROVES an exact byte width exists — a hoisted + // constant hides the number, not the constraint. Returning nothing published + // an unrestricted integer with no signal that the width was lost. + resolver.drop_note_keyed( + "contentUint width not statically resolvable", + site.to_string(), + ); + } + return (width, None); } if accessor != Some("contentBytesRange") { return (None, None); diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index 89faf66..c31fb72 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -826,7 +826,13 @@ struct IqConstraintCounts { field_enum_refs: usize, /// Response variants classified as a client or server error. typed_error_variants: usize, - /// Distinct `` values across every RPC's error vocabulary. + /// Text-pinned error arms, keyed by `(module, variant, arm, text)`. + /// + /// NOT the count of distinct texts. A global set of strings cannot fall when one RPC + /// loses its `bad-request` pin while another still carries it — and the arm survives + /// too, so `errorArms` does not move either, and the floor passes while that RPC's + /// accepted `(code, text)` pairing has silently degraded. Counting occurrences is what + /// makes the guard able to see a per-RPC loss. error_texts: usize, /// Accepted `(code, text)` error shapes across every RPC — the paired form, which /// the flattened `errorTexts` count cannot regress on its own. @@ -882,11 +888,25 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { ) { c.typed_error_variants += 1; } + // Keyed by the ARM this text pins, not by the text alone. A global set of + // distinct strings cannot fall when one RPC loses its `bad-request` pin while + // another still carries it — and the arm survives too, so `errorArms` does not + // move either, and the floor passes while a specific RPC's accepted + // `(code, text)` pairing silently degrades. texts.extend( v.error_arms .iter() .chain(v.error_envelope.as_ref()) - .filter_map(|a| a.text.clone()), + .filter(|a| a.text.is_some()) + .map(|a| { + format!( + "{}\u{1}{}\u{1}{}\u{1}{}", + s.module_name, + v.tag, + a.name.as_deref().unwrap_or_default(), + a.text.as_deref().unwrap_or_default() + ) + }), ); // Alternatives only. Folding the envelope in made the number disagree with // the artifact (646 reported against 645 present) and let an envelope diff --git a/generated/manifest.json b/generated/manifest.json index 5723f44..8de0dea 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -11,7 +11,7 @@ "candidateModules": 207, "constraints": { "errorArms": 645, - "errorTexts": 38, + "errorTexts": 577, "fieldEnumRefs": 150, "fieldLiterals": 1784, "referenceConstraints": 584, From c4d4c76d00070987bd89939e1644210c3357561a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 11:42:43 -0300 Subject: [PATCH 35/37] notif: bind helper parameters, and stop three shapes the extractor was inventing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 30 — six findings, two live. `fieldEnumRefs` counted only a resolved `enumRef`, ignoring `enumKeys` — the same closed allowed-value constraint, resolved in-module instead of cross-module. One such field is live (`type` accepting only `error`/`result`), so losing that table would have left the floor unmoved. 150 → 151. `string_const_object` accepted a partial map when the object held a spread: `obj_props` skips it, so `{ADD: "old", ...base}` resolved `ADD` to `old` while the runtime switch uses the spread's value, and a spread-only member vanished. Either way the arm gets a wireTag or actionType the handler never produces. Same rule the response-side enum tables got two rounds ago — a table that cannot be read whole is not a table. Three more shapes the reader was asserting rather than reading: * `c ? attrString("jid") : attrString("jid")` — the same read on both paths runs on every execution, but falling through to the plain path made `read_field` see a conditional and publish `required: false` for an accessor that still rejects an absent value. * `{meta: {id: …}}` in a mapped-child callback had its inner `id` hoisted into the child's flat fields, claiming a top-level attribute the element does not have. * a `do…while` whose body can `break` is not a definite write; the break path reaches the return with the binding unset. And the helper-parameter binding I declined in round 23 is implemented: a helper is now applied to its call-site argument TEXT (re-parsed in the same allocator, which is what makes the binding expressible), so `function mk(v){ return {id: v} }` called as `mk(t.attrString("jid"))` keeps `id`. Applied ONLY when an argument is itself a wire read — binding the node-passing form's formals to identifiers absent from this parse resolved them to nothing and cost 62 shape elements before I caught it. 566 tests, 12/12 schemas, determinism check clean. --- crates/wa-notif/src/actions.rs | 167 ++++++++++++++++++++++++++++++--- crates/wa-notif/src/tests.rs | 81 +++++++++++++++- crates/whatspec/src/main.rs | 7 +- generated/manifest.json | 2 +- 4 files changed, 240 insertions(+), 17 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 2ec3f0a..1bf146f 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -319,6 +319,14 @@ fn string_const_object(e: &Expression) -> Option { // A single-argument wrapper call: `Object.freeze({…})`, `n("$InternalEnum")({…})`. None => wa_oxc::as_object(arg_expr(as_call(e)?.arguments.first()?)?)?, }; + // A spread, a method or a computed key is SKIPPED by `obj_props`, so accepting the + // survivors publishes a partial map: `{ADD: "old", ...base}` resolves `ADD` to `old` + // when the runtime switch uses the spread's value, and a spread-only member vanishes. + // Either way the arm gets a `wireTag`/`actionType` the handler never produces — + // invented, not merely lost. Same rule as the response-side enum tables. + if obj.properties.len() != wa_oxc::obj_props(obj).count() { + return None; + } let mut map = ConstMap::new(); for (key, value) in wa_oxc::obj_props(obj) { map.insert(key.to_string(), as_string_lit(value)?.to_string()); @@ -366,6 +374,7 @@ pub(crate) fn extract_actions( } let ctx = ArmCtx { consts, + source: handler_slice, locals: by_name .into_iter() .filter_map(|(n, src)| src.map(|s| (n, s))) @@ -385,6 +394,10 @@ pub(crate) fn extract_actions( struct ArmCtx<'c, 'a> { consts: &'c ConstResolver<'a>, locals: HashMap, + /// The handler module's source, so an inlined helper can be given the TEXT of the + /// arguments it was called with. The helper is re-parsed in its own allocator, so a + /// call-site AST reference cannot cross into it — its source can. + source: &'c str, } /// Collects `function name(…){…}` / `var name = function(…){…}` spans in a module. @@ -1011,7 +1024,16 @@ fn collect_returns_into<'b, 'a>( // A `do…while` body runs AT LEAST once, so its writes are definite — the // ordinary loop merge tombstoned them and lost every field they bound. Statement::DoWhileStatement(d) => { - collect_returns_into(&[&d.body], scope, out); + // The body runs at least once, so its writes are definite — UNLESS a path + // leaves it early: `do { if (c) break; x = a("lid"); } while (…)` reaches + // the return without assigning `x`, and propagating unconditionally + // recorded `lid` as its one certain source. + if can_escape(&d.body, true) { + collect_returns(&[&d.body], scope, out); + tombstone_branch_writes(s, scope); + } else { + collect_returns_into(&[&d.body], scope, out); + } } Statement::ForStatement(_) | Statement::ForInStatement(_) @@ -1231,7 +1253,7 @@ fn expand_shape( if depth <= MAX_INLINE_DEPTH && let Some(src) = local_call_source(deref_ident(result, scope), ctx) { - let expanded = expand_helper(wire_tag, &src, ctx, depth + 1); + let expanded = expand_helper(wire_tag, &src.0, &src.1, ctx, depth + 1); if !expanded.is_empty() { return expanded; } @@ -1239,11 +1261,82 @@ fn expand_shape( vec![read_action(wire_tag.to_string(), result, scope, ctx, depth)] } +/// The helper source, immediately applied to its call-site arguments when one of them is +/// itself a wire read. +/// +/// Applied ONLY in that case. A helper handed the NODE (`y(chat, child, tag)` — the +/// overwhelmingly common form) already works: its body reads off its own parameter and +/// `find_accessor` finds that structurally. Binding those formals to call-site +/// identifiers that do not exist in this parse resolves them to nothing, and doing it +/// unconditionally cost 62 shape elements. The value-passing form is the one that needs +/// the binding. +fn apply_args(fn_src: &str, arg_srcs: &[String]) -> String { + let passes_a_read = arg_srcs + .iter() + .any(|a| a.contains(".attr") || a.contains(".maybeAttr") || a.contains(".content")); + if passes_a_read { + format!("(({fn_src})({}))", arg_srcs.join(", ")) + } else { + format!("({fn_src})") + } +} + +/// Split a parsed `((fn)(a, b))` into the function and a scope binding its formals to +/// the call-site argument expressions. +/// +/// Both live in the SAME allocator (the arguments were re-parsed from their source text +/// alongside the helper), which is what makes the binding expressible at all — a +/// call-site AST node from the enclosing parse could not cross into here. +fn applied_helper<'b, 'a>(e: &'b Expression<'a>) -> (&'b Expression<'a>, Scope<'b, 'a>) { + let mut scope = Scope::new(); + // The wrapper is `((fn)(args))`, so the outer parens hide the call. + let e = match e { + Expression::ParenthesizedExpression(p) => &p.expression, + other => other, + }; + let Some(call) = as_call(e) else { + return (e, scope); + }; + let func = &call.callee; + let params = match func { + Expression::ParenthesizedExpression(p) => function_params(&p.expression), + other => function_params(other), + }; + for (i, name) in params.into_iter().enumerate() { + if let Some(arg) = call.arguments.get(i).and_then(arg_expr) { + scope.insert(name, arg); + } + } + match func { + Expression::ParenthesizedExpression(p) => (&p.expression, scope), + other => (other, scope), + } +} + +/// The formal parameter names of a function expression / declaration. +fn function_params<'b, 'a>(e: &'b Expression<'a>) -> Vec<&'b str> { + let items = match e { + Expression::FunctionExpression(f) => &f.params.items, + Expression::ArrowFunctionExpression(f) => &f.params.items, + _ => return Vec::new(), + }; + items + .iter() + .filter_map(|p| p.pattern.get_identifier_name().map(|n| n.as_str())) + .collect() +} + /// Parse a helper and expand each of its result branches, inside this parse's own /// allocator (only the owned definitions escape). -fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec { +fn expand_helper( + wire_tag: &str, + fn_src: &str, + arg_srcs: &[String], + ctx: &ArmCtx, + depth: u8, +) -> Vec { let alloc = Allocator::default(); - let wrapped = format!("({fn_src})"); + let wrapped = apply_args(fn_src, arg_srcs); let ret = wa_oxc::parse_cjs(&alloc, &wrapped); if ret.panicked { return Vec::new(); @@ -1254,8 +1347,9 @@ fn expand_helper(wire_tag: &str, fn_src: &str, ctx: &ArmCtx, depth: u8) -> Vec = Vec::new(); - for (shape, scope) in fn_result_shapes(func, &Scope::new()) { + for (shape, scope) in fn_result_shapes(func, &bound) { for action in expand_shape(wire_tag, shape, &scope, ctx, depth) { match merged .iter_mut() @@ -1318,7 +1412,7 @@ fn fold_shape<'b, 'a>( if let Some(child) = mapped_child("", e, scope, ctx.consts) { def.children.push(child); } else if let Some(src) = local_call_source(e, ctx) { - inline_local(&src, def, ctx, depth); + inline_local(&src.0, &src.1, def, ctx, depth); } } } @@ -1329,20 +1423,45 @@ fn fold_shape<'b, 'a>( const MAX_INLINE_DEPTH: u8 = 3; /// The source of the module-local helper `e` calls, if it is one. -fn local_call_source(e: &Expression, ctx: &ArmCtx) -> Option { +fn local_call_source(e: &Expression, ctx: &ArmCtx) -> Option<(String, Vec)> { let call = as_call(e)?; let name = as_identifier(&call.callee)?; - ctx.locals.get(name).cloned() + let src = ctx.locals.get(name).cloned()?; + // The argument TEXT, so `inline_local` can bind the helper's formals. A span outside + // the module source (a synthesized node) yields nothing for that position rather than + // a wrong slice. + let args = call + .arguments + .iter() + .map(|a| { + arg_expr(a) + .map(|e| oxc_span::GetSpan::span(e)) + .and_then(|sp| ctx.source.get(sp.start as usize..sp.end as usize)) + .unwrap_or("") + .to_string() + }) + .collect(); + Some((src, args)) } /// Re-parse a helper's source and fold each of its result branches into `def`. /// /// The inlining happens inside this parse's own allocator, so no AST reference escapes /// it — the helper's contribution is accumulated straight into `def`. -fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) { +fn inline_local( + fn_src: &str, + arg_srcs: &[String], + def: &mut NotifActionDef, + ctx: &ArmCtx, + depth: u8, +) { let alloc = Allocator::default(); - // Wrapped in parens so a `function name(…){…}` declaration parses as an expression. - let wrapped = format!("({fn_src})"); + // Wrapped in parens so a `function name(…){…}` declaration parses as an expression, + // and IMMEDIATELY APPLIED to the call site's argument text. A helper that only reads + // off a node it is handed works either way, but one that receives an already-read + // VALUE (`function h(v){ return {id: v} }` called as `h(child.attrString("jid"))`) + // resolves its parameter to nothing without this, and the field is lost. + let wrapped = apply_args(fn_src, arg_srcs); let ret = wa_oxc::parse_cjs(&alloc, &wrapped); if ret.panicked { return; @@ -1353,13 +1472,14 @@ fn inline_local(fn_src: &str, def: &mut NotifActionDef, ctx: &ArmCtx, depth: u8) }) else { return; }; + let (func, bound) = applied_helper(func); // Each of the helper's result branches is folded on its own and then MERGED, not // accumulated: a helper returning `{x: …}` in one branch and `{y: …}` in another // describes two legal shapes, and combining them would make the enclosing action // require both and reject either. `merge_action` weakens what only some branches // carry — the same rule the switch arms use. let mut branches: Vec = Vec::new(); - for (shape, scope) in fn_result_shapes(func, &Scope::new()) { + for (shape, scope) in fn_result_shapes(func, &bound) { let mut one = empty_action(String::new()); fold_shape(shape, &scope, &mut one, ctx, depth + 1); match branches @@ -1493,7 +1613,7 @@ fn fold_object<'b, 'a>( && depth < MAX_INLINE_DEPTH { let mut nested = empty_action(String::new()); - inline_local(&src, &mut nested, ctx, depth); + inline_local(&src.0, &src.1, &mut nested, ctx, depth); // A helper that yields nothing is a helper the inliner cannot see through — // typically because the value arrives through a PARAMETER // (`reason: hasAttr("reason") ? S(t.attrString("reason")) : null`, where `S` @@ -1646,7 +1766,18 @@ fn value_selection<'b, 'a>( (None, Some(acc)) if is_nullish(a) => Some(Some(guarded_field(key, acc, consts))), (Some(x), Some(y)) => { if x.wire_name == y.wire_name && x.method == y.method && x.content == y.content { - None // same read on both paths — the plain path handles it + // The SAME read on both paths runs on every execution, so it keeps the + // accessor's own requiredness. Falling through to the plain path made + // `read_field` see a conditional, call it a presence guard, and publish + // `required: false` for an accessor that still rejects an absent value. + Some(Some(NotifActionField { + name: key.to_string(), + required: !wap::is_optional_method(&x.method), + field_type: wap::method_field_type(&x.method), + wire_name: x.wire_name, + content: x.content, + enum_ref: x.enum_arg.and_then(|a| consts.enum_ref(a)), + })) } else { Some(None) // two sources for one key: refuse } @@ -1969,6 +2100,14 @@ fn collect_shape_fields<'b, 'a>( { self.out.push(field); } + // Do NOT descend into a property whose value is itself an object literal: + // that is NESTING, not more of this shape. `{meta: {id: p.attrString("id")}}` + // has no top-level `id`, and walking in claimed each element had one. The walk + // still descends everything else, which is how it reaches the operands of a + // `babelHelpers.extends(…)` wrapper. + if wa_oxc::as_object(deref_ident(&p.value, self.scope)).is_some() { + return; + } walk::walk_object_property(self, p); } } diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index 8a736d3..a48af0b 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp",DOWX:"dowx"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp",DOWX:"dowx",SAME:"same",NEST:"nest",DOWB:"dowb",PVAL:"pval",SPREAD:"spread"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1237,6 +1237,7 @@ __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGroupType"],(function(t,n,r,o,a,i,l){ function hlp(e){ return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, extra:e.attrString("extra")}; } function norm(v){ return v == null ? null : v; } + function mk(v){ return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, id:v}; } function h(e){ var x=e.mapChildrenWithTag("child", function(t){ switch (t.tag) { @@ -1336,6 +1337,17 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DOWX: do { return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, a:t.attrString("a")}; } while (t.hasChild("again")); return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SAME: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:t.hasChild("q") ? t.attrString("jid") : t.attrString("jid")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.NEST: + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, who:t.mapChildrenWithTag("p", function(p){ return {meta:{id:p.attrString("id")}, top:p.attrString("top")}; })}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DOWB: { + var db; + do { if (t.hasChild("skip")) break; db = t.attrString("lid"); } while (t.hasChild("again")); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:db}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.PVAL: + return mk(t.attrString("jid")); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1898,3 +1910,70 @@ fn nothing_after_an_exiting_do_while_is_reachable() { .collect(); assert_eq!(got, ["first"], "only the reachable shape: {got:?}"); } + +#[test] +fn an_identical_read_on_both_ternary_branches_stays_required() { + // `c ? attrString("jid") : attrString("jid")` runs on every path and still rejects an + // absent `jid`. Falling through to the plain path made `read_field` see a conditional, + // call it a presence guard, and publish `required: false`. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "same"); + let f = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the id field"); + assert_eq!(f.wire_name, "jid"); + assert!(f.required, "the accessor is required on every path: {f:?}"); +} + +#[test] +fn a_nested_callback_object_is_not_flattened_into_the_child() { + // `{meta: {id: p.attrString("id")}, top: …}` — the element has `meta.id`, NOT a + // top-level `id`. Walking every property value reached the inner one and claimed it. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "nest"); + let child = a + .children + .iter() + .find(|c| c.name == "who") + .expect("the child"); + let names: Vec<&str> = child.fields.iter().map(|f| f.name.as_str()).collect(); + assert!( + names.contains(&"top"), + "the real flat field survives: {names:?}" + ); + assert!( + !names.contains(&"id"), + "the nested one is not hoisted: {names:?}" + ); +} + +#[test] +fn a_do_while_that_can_break_early_is_not_a_definite_write() { + // `do { if (c) break; x = a("lid"); } while (…)` reaches the return without assigning + // `x` on the break path, so `lid` is not its one certain source. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "dowb"); + assert!( + !a.fields.iter().any(|f| f.name == "id"), + "the early-exit path leaves it unbound: {:?}", + a.fields + ); +} + +#[test] +fn a_helper_given_a_wire_read_binds_its_parameter() { + // `function mk(v){ return {actionType: THIRD, id: v} }` called as + // `mk(t.attrString("jid"))`. The helper reads no attribute of its own, so expanding it + // with an empty scope resolved `v` to nothing and lost `id`. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "pval"); + assert_eq!(a.action_type.as_deref(), Some("third")); + let f = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the id field"); + assert_eq!(f.wire_name, "jid"); +} diff --git a/crates/whatspec/src/main.rs b/crates/whatspec/src/main.rs index c31fb72..73cccc6 100644 --- a/crates/whatspec/src/main.rs +++ b/crates/whatspec/src/main.rs @@ -823,6 +823,8 @@ struct IqConstraintCounts { /// `ParsedField`s carrying a pinned `literalValue`. field_literals: usize, /// Response fields whose enum argument resolved to a real variant set. + /// Response fields carrying a resolved wire-enum constraint, whether it arrived as a + /// cross-module `enumRef` or an in-module `enumKeys` set. field_enum_refs: usize, /// Response variants classified as a client or server error. typed_error_variants: usize, @@ -849,7 +851,10 @@ fn iq_constraint_counts(ir: &wa_ir::IqIr) -> IqConstraintCounts { if f.reference_path.is_some() { c.reference_constraints += 1; } - if f.enum_ref.is_some() { + // `enumKeys` is the SAME closed allowed-value constraint, just resolved + // in-module instead of cross-module. Counting only `enumRef` meant losing an + // `enumKeys` table left the floor unmoved — one such field is live today. + if f.enum_ref.is_some() || f.enum_keys.as_ref().is_some_and(|k| !k.is_empty()) { c.field_enum_refs += 1; } if let Some(children) = &f.children { diff --git a/generated/manifest.json b/generated/manifest.json index 8de0dea..80ea840 100644 --- a/generated/manifest.json +++ b/generated/manifest.json @@ -12,7 +12,7 @@ "constraints": { "errorArms": 645, "errorTexts": 577, - "fieldEnumRefs": 150, + "fieldEnumRefs": 151, "fieldLiterals": 1784, "referenceConstraints": 584, "typedErrorVariants": 104 From 93ae2139427b74070f0e2d832f8e87e034ec972e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Tue, 28 Jul 2026 11:43:19 -0300 Subject: [PATCH 36/37] notif: drop a redundant closure clippy flagged --- crates/wa-notif/src/actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 1bf146f..5e6c962 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -1435,7 +1435,7 @@ fn local_call_source(e: &Expression, ctx: &ArmCtx) -> Option<(String, Vec Date: Tue, 28 Jul 2026 12:05:05 -0300 Subject: [PATCH 37/37] notif: five more JS-semantics corrections in the action reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 31 — eight findings, five applied, three deferred with evidence (below). * a `break` consumed by a `do…while` cannot prove the loop exits its CALLER, so `do { break; } while (c); return B` stopped before the reachable B. * a `finally` runs on every path, so its writes are definite; tombstoning the whole `try` statement removed them along with the uncertain ones. * a `for` INITIALIZER runs before every path into the body, so the body must be analyzed against it, not against the pre-loop scope. * a write buried in a short-circuit or ternary (`c && (x = a("lid"))`) runs on some paths only; ignoring it left the previous binding asserted as fact. * a returned action object containing a spread cannot be read whole, so it is refused — the same rule the constant tables and enum tables already follow. 571 tests; each fix reverted alone to watch its test fail. Artifact unchanged. DEFERRED, and the reason is worth recording. `generated/notif/index.json` has not moved since cb533c8 — eight commits and six rounds of work in this file. Every finding since has been a JavaScript construct the hand-rolled dataflow analysis in `actions.rs` does not model, and three of the eight this round (`do…while` at all, an object spread, a by-name mapped-child callback) do not occur anywhere in the pinned handler module. The three left out — resolving an accessor ALIAS passed to a helper, resolving a named callback through the local index, and marking a branch-only child collection optional — are real and each needs new plumbing or an IR field. That is a poor trade at this point, and not only on cost: patching this analysis construct-by-construct has itself introduced regressions in rounds 19, 23, 24, 26 and 30, including one in this very round's predecessor that cost 62 shape elements before a counter caught it. The class wants either a documented scope boundary or a real dataflow pass, not another round of constructs. --- crates/wa-notif/src/actions.rs | 123 ++++++++++++++++++++++++++++++++- crates/wa-notif/src/tests.rs | 104 +++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 4 deletions(-) diff --git a/crates/wa-notif/src/actions.rs b/crates/wa-notif/src/actions.rs index 5e6c962..b656886 100644 --- a/crates/wa-notif/src/actions.rs +++ b/crates/wa-notif/src/actions.rs @@ -684,7 +684,9 @@ fn stmt_exits(s: &Statement, nested: bool) -> bool { // A `do…while` body runs at least once, so if it exits on every path the loop // does. `do { return A } while (c); return B` was collecting A and then walking // on to publish the unreachable B. - Statement::DoWhileStatement(d) => stmt_exits(&d.body, nested), + // The body's own `break`/`continue` are consumed by THIS loop, so they cannot + // prove it exits its caller: `do { break; } while (c); return B` reaches B. + Statement::DoWhileStatement(d) => !can_escape(&d.body, true) && stmt_exits(&d.body, nested), Statement::SwitchStatement(sw) => { sw.cases.iter().any(|c| c.test.is_none()) && (0..sw.cases.len()).all(|i| { @@ -890,6 +892,13 @@ fn collect_returns_into<'b, 'a>( // `SequenceExpression`; matching only a bare assignment skipped those // entirely and left the stale initializer installed. Statement::ExpressionStatement(e) => { + // A write buried in a short-circuit or ternary runs on SOME paths only: + // `cond && (x = attrString("lid"))` leaves `x` holding either value, so + // the name no longer has one known source and is tombstoned. Ignoring it + // left the pre-expression binding installed and published it as fact. + for name in conditional_writes(&e.expression) { + scope.remove(name); + } let mut ops = Vec::new(); flatten_sequence(&e.expression, &mut ops); for a in ops { @@ -991,8 +1000,15 @@ fn collect_returns_into<'b, 'a>( out.extend(paths); } out.extend(finalizer); - // A `try` body may have run in part; nothing it wrote is certain after it. + // A `try` body may have run in part; nothing IT wrote is certain after it. + // The FINALIZER is different: it runs on every path, so its writes are + // definite and tombstoning them lost fields the return always reads. + // Uncertain try/catch writes are dropped first, then the finalizer's are + // replayed over the result. tombstone_branch_writes(s, scope); + if let Some(f) = &t.finalizer { + replay_writes(&f.body, scope); + } } // A nested `switch` inside an arm (or a helper body) is how THAT arm picks // its shape — `case LINK: switch (linkType) { case "parent": return {…}; @@ -1039,8 +1055,38 @@ fn collect_returns_into<'b, 'a>( | Statement::ForInStatement(_) | Statement::ForOfStatement(_) | Statement::WhileStatement(_) => { + // A `for` INITIALIZER runs before every path that enters the body, so it + // is a definite write for the body's scope — `for (x = a("lid"); …;) + // return {id: x}` always reads `lid`, and analyzing the body against the + // pre-loop scope published the stale source. + let mut entry = scope.clone(); + if let Statement::ForStatement(f) = s { + match &f.init { + Some(oxc_ast::ast::ForStatementInit::VariableDeclaration(d)) => { + for decl in &d.declarations { + if let (Some(n), Some(init)) = + (decl.id.get_identifier_name(), decl.init.as_ref()) + { + entry.insert(n.as_str(), init); + } + } + } + Some(init) => { + if let Some(e) = init.as_expression() { + let mut ops = Vec::new(); + flatten_sequence(e, &mut ops); + for a in ops { + if let Some(n) = a.left.get_identifier_name() { + entry.insert(n, &a.right); + } + } + } + } + None => {} + } + } if let Some(body) = loop_body(s) { - collect_returns(&[body], scope, out); + collect_returns(&[body], &entry, out); } tombstone_branch_writes(s, scope); } @@ -1104,6 +1150,70 @@ fn flatten_sequence<'b, 'a>( } } +/// Names assigned inside a CONDITIONAL part of an expression — an operand of `&&`/`||` +/// or either arm of a ternary. Definite top-level writes are excluded; [`flatten_sequence`] +/// handles those. +fn conditional_writes<'b>(e: &'b Expression) -> Vec<&'b str> { + fn walk<'b>(e: &'b Expression, guarded: bool, out: &mut Vec<&'b str>) { + match e { + Expression::AssignmentExpression(a) => { + if guarded && let Some(n) = a.left.get_identifier_name() { + out.push(n); + } + walk(&a.right, guarded, out); + } + Expression::LogicalExpression(l) => { + walk(&l.left, guarded, out); + walk(&l.right, true, out); + } + Expression::ConditionalExpression(c) => { + walk(&c.test, guarded, out); + walk(&c.consequent, true, out); + walk(&c.alternate, true, out); + } + Expression::SequenceExpression(seq) => { + for sub in &seq.expressions { + walk(sub, guarded, out); + } + } + Expression::ParenthesizedExpression(p) => walk(&p.expression, guarded, out), + _ => {} + } + } + let mut out = Vec::new(); + walk(e, false, &mut out); + out +} + +/// Re-apply the writes a statement list performs, for a construct that runs on EVERY +/// path (a `finally`). Only top-level declarations and assignments; anything conditional +/// inside stays out, which is what keeps this from re-asserting an uncertain value. +fn replay_writes<'b, 'a>(stmts: &'b [Statement<'a>], scope: &mut Scope<'b, 'a>) { + for st in stmts { + match st { + Statement::VariableDeclaration(d) => { + for decl in &d.declarations { + if let (Some(n), Some(init)) = + (decl.id.get_identifier_name(), decl.init.as_ref()) + { + scope.insert(n.as_str(), init); + } + } + } + Statement::ExpressionStatement(e) => { + let mut ops = Vec::new(); + flatten_sequence(&e.expression, &mut ops); + for a in ops { + if let Some(n) = a.left.get_identifier_name() { + scope.insert(n, &a.right); + } + } + } + _ => {} + } + } +} + /// Drop every binding a conditional construct may have reassigned, so statements after /// the rejoin do not read a value that only one path wrote. fn tombstone_branch_writes(s: &Statement, scope: &mut Scope) { @@ -1576,6 +1686,13 @@ fn fold_object<'b, 'a>( let Some(o) = wa_oxc::as_object(obj) else { return; }; + // `obj_props` skips a spread, so folding the survivors publishes a partial shape: + // `{id: attrString("jid"), ...{id: attrString("lid")}}` yields `lid` at runtime and + // `jid` here. Same rule the constant tables and the enum tables already follow — a + // shape that cannot be read whole is refused rather than half-reported. + if o.properties.len() != wa_oxc::obj_props(o).count() { + return; + } for (key, value) in wa_oxc::obj_props(o) { // The normalised action identity. Resolved through the const table, so the // many-to-one mapping (`not_ephemeral` → `ephemeral`) is recorded as WA means diff --git a/crates/wa-notif/src/tests.rs b/crates/wa-notif/src/tests.rs index a48af0b..4db5bd6 100644 --- a/crates/wa-notif/src/tests.rs +++ b/crates/wa-notif/src/tests.rs @@ -1229,7 +1229,7 @@ __d("WAWebCommsHandleLoggedInStanza",["WAWebHandleGroupNotification"],function(g }); }; }, 1); __d("WAWebHandleGroupNotificationConst",[],(function(t,n,r,o,a,i,l){ - l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp",DOWX:"dowx",SAME:"same",NEST:"nest",DOWB:"dowb",PVAL:"pval",SPREAD:"spread"}); + l.GROUP_NOTIFICATION_TAG=Object.freeze({EVICT:"evict",COND:"cond",REBIND:"rebind",PICK:"pick",BARE:"bare",HELPER:"helper",ESCAPE:"escape",TAIL:"tail",JOIN:"join",SEQ:"seq",SUFFIX:"suffix",AFTER:"after",TRY:"try_tag",FIN:"fin",FINCOND:"fincond",FINTHROW:"finthrow",MULTI:"multi",MULTI3:"multi3",DEAD:"dead",LIT:"lit",LOOP:"loop_tag",FINTRY:"fintry",PARAM:"param",BLK:"blk",EXH:"exh",NFT:"nft",LATE:"late",DOW:"dow",SHADOW:"shadow",CATCHP:"catchp",DOWX:"dowx",SAME:"same",NEST:"nest",DOWB:"dowb",PVAL:"pval",SPREAD:"spread",DOWBRK:"dowbrk",FINW:"finw",FORI:"fori",CONDW:"condw",SPRD:"sprd"}); }), 1); __d("WAWebGroupType",[],(function(t,n,r,o,a,i,l){ l.GROUP_ACTIONS=Object.freeze({FIRST:"first",SECOND:"second",THIRD:"third"}); @@ -1348,6 +1348,26 @@ __d("WAWebHandleGroupNotification",["WAWebHandleGroupNotificationConst","WAWebGr } case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.PVAL: return mk(t.attrString("jid")); + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.DOWBRK: + do { break; } while (t.hasChild("x")); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, b:t.attrString("b")}; + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FINW: { + var fw; + try { fw = t.attrString("jid"); } finally { fw = t.attrString("lid"); } + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:fw}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.FORI: { + var fi = t.attrString("jid"); + for (fi = t.attrString("lid"); t.hasChild("y"); ) return {actionType:o("WAWebGroupType").GROUP_ACTIONS.SECOND, id:fi}; + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.THIRD, z:t.attrString("z")}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.CONDW: { + var cw = t.attrString("jid"); + t.hasChild("c") && (cw = t.attrString("lid")); + return {actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST, id:cw}; + } + case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SPRD: + return babelHelpers.extends({actionType:o("WAWebGroupType").GROUP_ACTIONS.FIRST}, {id:t.attrString("jid"), ...base}); case o("WAWebHandleGroupNotificationConst").GROUP_NOTIFICATION_TAG.SUFFIX: switch (t.attrString("k")) { case "a": t.attrString("setup"); @@ -1977,3 +1997,85 @@ fn a_helper_given_a_wire_read_binds_its_parameter() { .expect("the id field"); assert_eq!(f.wire_name, "jid"); } + +#[test] +fn a_break_consumed_by_a_do_while_does_not_end_the_case() { + // `do { break; } while (c); return B` reaches B — the `break` leaves the LOOP, not the + // case. Passing the enclosing `nested` down made `stmt_exits` treat it as an exit. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let got: Vec<&str> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "dowbrk") + .filter_map(|a| a.action_type.as_deref()) + .collect(); + assert_eq!( + got, + ["second"], + "the statement after the loop is reachable: {got:?}" + ); +} + +#[test] +fn a_finalizer_write_is_definite() { + // A `finally` runs on every path, so what it assigns is certain at the return. + // Tombstoning the whole `try` statement removed the finalizer's own writes too. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "finw"); + let f = a + .fields + .iter() + .find(|f| f.name == "id") + .expect("the id field"); + assert_eq!( + f.wire_name, "lid", + "the finalizer's write is what reaches the return" + ); +} + +#[test] +fn a_for_initializer_runs_before_the_body() { + // `for (x = a("lid"); c; ) return {id: x}` always reads `lid`; analyzing the body + // against the pre-loop scope published the stale `jid`. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let second: Vec<_> = notif(&ir, "w:gp2") + .actions + .iter() + .filter(|a| a.wire_tag == "fori" && a.action_type.as_deref() == Some("second")) + .collect(); + assert_eq!(second.len(), 1); + let f = second[0] + .fields + .iter() + .find(|f| f.name == "id") + .expect("id"); + assert_eq!(f.wire_name, "lid"); +} + +#[test] +fn a_conditional_expression_write_tombstones_the_binding() { + // `cond && (x = a("lid"))` runs on some paths only, so `x` no longer has one known + // source. Ignoring the nested assignment left the pre-expression `jid` asserted. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "condw"); + assert!( + !a.fields + .iter() + .any(|f| f.name == "id" && f.wire_name == "jid"), + "the stale source must not be published: {:?}", + a.fields + ); +} + +#[test] +fn an_action_object_with_a_spread_is_refused() { + // `obj_props` skips the spread, so folding the survivors would publish a partial + // shape. A shape that cannot be read whole is refused, like the tables. + let ir = extract_notif(GROUP_ACTIONS_EDGE_BUNDLE, "2.3000.test"); + let a = edge_action(&ir, "sprd"); + assert!( + !a.fields.iter().any(|f| f.name == "id"), + "no partial shape is published: {:?}", + a.fields + ); +}