Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
7be1924
test: add source-backed IQ and MEX conformance baseline
jlucaso1 Oct 7, 2026
0439f9a
feat: add evidence-based snapshot contract review
jlucaso1 Oct 7, 2026
0c69268
fix(codegen): reject degraded IQ response parsers explicitly
jlucaso1 Oct 7, 2026
1b9286d
ci: run offline conformance fixture checks independently
jlucaso1 Oct 7, 2026
1fb26b8
test: report verified schema pair counts explicitly
jlucaso1 Oct 7, 2026
92bd61c
fix: bind delta reviews to both artifact hashes
jlucaso1 Oct 7, 2026
2408fbf
fix: update rustls for RUSTSEC-2026-0285
jlucaso1 Oct 7, 2026
585ff93
fix: update rustls for RUSTSEC-2026-0285
jlucaso1 Oct 7, 2026
d408c84
fix(codegen): preserve pilot IQ outcomes and request correlation
jlucaso1 Oct 7, 2026
6267495
fix: update rustls for RUSTSEC-2026-0285
jlucaso1 Oct 7, 2026
ebbfaed
test: bind conformance AST capture to verified bytes
jlucaso1 Oct 7, 2026
a79c93f
test(codegen): document source-ordered malformed error fallback
jlucaso1 Oct 7, 2026
1f847fe
test: compose conformance and IQ pilot heads locally
jlucaso1 Oct 7, 2026
a863bfb
test: expose binary error-content divergence in composed IQ pilots
jlucaso1 Oct 7, 2026
4d1bed4
feat: trace counted snapshot gaps to checked AST source
jlucaso1 Oct 7, 2026
58afb2e
fix: recover proven local object arguments in WAM constructions
jlucaso1 Oct 7, 2026
9e609d4
fix(codegen): reject binary content in optional child error arms
jlucaso1 Oct 7, 2026
726d59e
test: repin snapshot gaps after source-level attribution
jlucaso1 Oct 7, 2026
0ac0b79
Merge commit '9e609d4dee4c80ec50bc42a75110e69db13400d8' into test/com…
jlucaso1 Oct 7, 2026
440abf3
test: qualify IQ binary-content fix across both snapshots
jlucaso1 Oct 7, 2026
1d5d7f3
fix: include nested definitions in checked provenance index
jlucaso1 Oct 7, 2026
374c5a1
Merge commit '1d5d7f3323a6b71493a1bd68b3a326dfb468b361' into test/com…
jlucaso1 Oct 7, 2026
7ea45e4
test: verify compiled fixture identities and preserve server parser
jlucaso1 Oct 7, 2026
eaea5f9
fix: validate schema reference closure before instances
jlucaso1 Oct 7, 2026
8bbce4e
Merge commit 'eaea5f9e5d864ee9d5ef4074709eddb9aba51735' into test/com…
jlucaso1 Oct 7, 2026
cd0f9da
test: run quality regressions in composed CI and record joint evidence
jlucaso1 Oct 7, 2026
810a81b
fix: avoid overlapping WAM scans in gap reports
jlucaso1 Oct 7, 2026
20d1efa
fix: give workspace generation examples unique Cargo output names
jlucaso1 Oct 7, 2026
1a96e67
fix: recover merge locals and trace unknown WAM fields
jlucaso1 Oct 7, 2026
d6985d2
fix: include declared schemas in contract comparisons
jlucaso1 Oct 7, 2026
106c762
fix: record provenance selection scope and review evidence
jlucaso1 Oct 7, 2026
529df26
Merge commit '106c7621348935daa343edf73ebc6ad50ad798a8' into test/com…
jlucaso1 Oct 7, 2026
79da4d9
test: record independent quality follow-up validation and remaining gap
jlucaso1 Oct 7, 2026
04200c9
fix: separate WAM counters without source locations
jlucaso1 Oct 7, 2026
5320071
fix: mark unbound snapshot input provenance as unknown
jlucaso1 Oct 7, 2026
2b97bd8
fix(wam): honor constructor merge overwrite order
jlucaso1 Oct 7, 2026
da32bc8
fix(wam): preserve object property overwrite order
jlucaso1 Oct 7, 2026
d067d60
fix(wam): invalidate constants after opaque setters
jlucaso1 Oct 7, 2026
e12000f
Merge commit 'd067d60b4042455aa9fb1c0fa4850b7e93fff214' into test/com…
jlucaso1 Oct 7, 2026
ae274e3
test: qualify final provenance semantics and document pilot limits
jlucaso1 Oct 7, 2026
0a7a4dd
ci: run snapshot validation regression tests
jlucaso1 Oct 8, 2026
c9eabb4
fix: reject whole-file parser recovery before conformance capture
jlucaso1 Oct 8, 2026
93abf96
Merge commit '0a7a4dda92e6ca98c02d2ab2fcf4aaa0662b29d0' into HEAD
jlucaso1 Oct 8, 2026
d6dbc66
Merge commit '9b8bc4e05eec14fa49b488c8eb8e56ce8ac5bfdb' into test/ind…
jlucaso1 Oct 8, 2026
9c8075d
Merge commit '9b8bc4e05eec14fa49b488c8eb8e56ce8ac5bfdb' into HEAD
jlucaso1 Oct 8, 2026
11f0252
test: validate baseline capture on merged quality base
jlucaso1 Oct 8, 2026
9086449
Merge commit '11f02520cdae081c535a618763a1a3d6604a507d' into HEAD
jlucaso1 Oct 8, 2026
09adb8b
Merge main into IQ response admission after quality integration
jlucaso1 Oct 8, 2026
ee45fff
test: pin full capture and input provenance bindings
jlucaso1 Oct 8, 2026
9356dee
test: pin reviewed baseline provenance records
jlucaso1 Oct 8, 2026
8173521
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
ae67cef
Merge commit '09adb8b7809b85dba8fdde4d3ebd7a3a4d0aed1b' into HEAD
jlucaso1 Oct 8, 2026
9de5d0a
fix(codegen): preserve IQ assertion semantics during admission
jlucaso1 Oct 8, 2026
686a7b8
test: integrate IQ assertion admission fixes for qualification
jlucaso1 Oct 8, 2026
4471cbc
fix(codegen): prove payload coverage and enforce response guards
jlucaso1 Oct 8, 2026
6936004
test: integrate payload-aware IQ guards and accessor adapter
jlucaso1 Oct 8, 2026
b8945bc
test: qualify IQ accessor and binary fallback guard updates
jlucaso1 Oct 8, 2026
3cb0863
test: close independent oracle coverage and fixture inventory gaps
jlucaso1 Oct 8, 2026
4808802
test: integrate reviewed baseline oracle fixes
jlucaso1 Oct 8, 2026
8581d29
test: assert complete presence keys and mirrored IQ contracts
jlucaso1 Oct 8, 2026
2ba95ca
test: integrate complete baseline presence and IQ mirror oracles
jlucaso1 Oct 8, 2026
d663231
test: bind IQ fixtures to Cargo artifacts and selected rustc
jlucaso1 Oct 8, 2026
4f8fec2
test: integrate Cargo-bound fixture compiler support
jlucaso1 Oct 8, 2026
92f2adf
test: use Cargo-bound compiler for independent IQ snapshots
jlucaso1 Oct 8, 2026
4352b88
Merge remote-tracking branch 'origin/main' into test/independent-conf…
jlucaso1 Oct 8, 2026
5518556
Merge remote-tracking branch 'origin/main' into HEAD
jlucaso1 Oct 8, 2026
3b5d8b9
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
a7b0cde
test: pin pilot exports, captured catalog and archive origins
jlucaso1 Oct 8, 2026
e999716
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
b36fd17
test: assert complete captured MEX catalog and variable shape
jlucaso1 Oct 8, 2026
5c4ee44
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
700310f
test: compare complete MEX presence nodes
jlucaso1 Oct 8, 2026
83c824f
docs: distinguish current pilot qualification from historical failures
jlucaso1 Oct 8, 2026
684a48c
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
33c2b3d
test: bind MEX input triage to reviewed operation identity
jlucaso1 Oct 8, 2026
9be0d75
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
e9a1fb0
test: pin outer IQ guards, parser identities and fragment diagnostics
jlucaso1 Oct 8, 2026
5ac24f7
Merge branch 'test/independent-conformance-baseline' into HEAD
jlucaso1 Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ permissions:
contents: read

jobs:
conformance-fixtures:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.x'
# Uses only the standard library and committed fixtures. No bundle restore
# or compiled extractor is needed for integrity and rejection checks.
- name: Verify conformance fixture integrity and capture rejection
run: python3 tests/conformance/test_capture.py -v

check:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -35,6 +50,9 @@ jobs:
- name: Test
run: cargo test --workspace

- name: Test conformance capture parser
run: cargo test -p wa-transform --example module_spans

# The JSON Schema derivation + envelope self-tests only build under the
# `schema` feature, so run them explicitly.
- name: Schema self-tests
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Extract AppState schemas from bundle file(s) (concatenated) and print JSON.
//! Run: cargo run -p wa-appstate --example gen -- <version> <bundle.js>...
//! Run: cargo run -p wa-appstate --example gen_appstate -- <version> <bundle.js>...

fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1);
let version = args.next().expect("usage: gen <version> <bundle.js>...");
let version = args
.next()
.expect("usage: gen_appstate <version> <bundle.js>...");
let mut source = String::new();
for path in args {
source.push_str(&std::fs::read_to_string(&path)?);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! Scan a bundle for IQ stanzas and generate the Rust `IqSpec` file.
//! Run: cargo run -p wa-codegen --example gen -- <bundle.js> <outdir>
//! cargo run -p wa-codegen --example gen -- <bundle.js> --ir (dump IqIr JSON)
//! Run: cargo run -p wa-codegen --example gen_iq -- <bundle.js> <outdir>
//! cargo run -p wa-codegen --example gen_iq -- <bundle.js> --ir (dump IqIr JSON)

fn main() -> anyhow::Result<()> {
let bundle = std::env::args()
.nth(1)
.expect("usage: gen <bundle.js> <outdir|--ir>");
.expect("usage: gen_iq <bundle.js> <outdir|--ir>");
let target = std::env::args()
.nth(2)
.expect("usage: gen <bundle.js> <outdir|--ir>");
.expect("usage: gen_iq <bundle.js> <outdir|--ir>");
let source = std::fs::read_to_string(&bundle)?;

let scan = wa_scan::scan_iq_stanzas(&source);
Expand Down
320 changes: 320 additions & 0 deletions crates/wa-codegen/tests/fixtures/independent_iq_cases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
// Expectations are manually read from preserved Web module bodies. No expected
// result is generated from IQ IR, codegen output or the other pilot's test main.
fn specs() -> (MakeSetSubjectRequestSpec, MakeAcceptGroupAddRequestSpec) {
let target = jid::Jid::new("123", jid::Server::Group);
(
MakeSetSubjectRequestSpec::new(target.clone(), "Olá & <grupo>".as_bytes().to_vec()),
MakeAcceptGroupAddRequestSpec::new(
"invite-42",
98765,
&jid::Jid::new("456", jid::Server::Pn),
target,
),
)
}
fn subject_class(node: &Node) -> Result<&'static str, anyhow::Error> {
Ok(
match specs()
.0
.parse_response_with_request(&node.as_ref(), "req-1", "123@g.us")?
{
MakeSetSubjectRequestResponse::Success(_) => "success",
MakeSetSubjectRequestResponse::ClientError(_) => "client",
MakeSetSubjectRequestResponse::ServerError(_) => "server",
},
)
}
fn accept_class(node: &Node) -> Result<&'static str, anyhow::Error> {
Ok(
match specs()
.1
.parse_response_with_request(&node.as_ref(), "req-1", "123@g.us")?
{
MakeAcceptGroupAddRequestResponse::Success(_) => "success",
MakeAcceptGroupAddRequestResponse::GroupJoinRequestSuccess(_) => "approval",
MakeAcceptGroupAddRequestResponse::ClientError(_) => "client",
MakeAcceptGroupAddRequestResponse::ServerError(_) => "server",
},
)
}
#[test]
fn request_destination_and_content() {
let (subject, accept) = specs();
let request = subject.build_iq();
assert_eq!(request.target.0, "123@g.us");
assert_eq!((request.namespace, request.kind), ("w:g2", "set"));
let node::NodeContent::Nodes(nodes) = request.content.unwrap();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].tag, "subject");
assert_eq!(nodes[0].bytes.as_deref(), Some("Olá & <grupo>".as_bytes()));
let request = accept.build_iq();
assert_eq!(request.target.0, "123@g.us");
assert_eq!((request.namespace, request.kind), ("w:g2", "set"));
let node::NodeContent::Nodes(nodes) = request.content.unwrap();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].tag, "accept");
assert_eq!(
nodes[0].attrs,
BTreeMap::from([
("code".into(), "invite-42".into()),
("expiration".into(), "98765".into()),
("admin".into(), "456@s.whatsapp.net".into())
])
);
}
#[test]
fn success_order_and_runtime_extensions() {
let approval = Node::new("membership_approval_request", &[], vec![]);
for (children, expected) in [
(vec![], "success"),
(vec![approval.clone()], "approval"),
(vec![approval.clone(), approval], "success"),
(vec![Node::new("future", &[], vec![])], "success"),
] {
let node = result(children);
assert_eq!(subject_class(&node).unwrap(), "success");
assert_eq!(accept_class(&node).unwrap(), expected);
}
// Bare success does not examine children or content. A failed approval
// selector is followed by bare success, even with binary root content.
let mut node = result(vec![]);
node.bytes = Some(vec![0xff]);
assert_eq!(accept_class(&node).unwrap(), "success");
assert_eq!(subject_class(&node).unwrap(), "success");
}
#[test]
fn ordered_error_outcomes_and_ranges() {
for (code, text, accept, subject) in [
("304", "already-exists", Some("client"), None),
("304", "bad-request", None, None),
("399", "unknown", None, None),
("400", "bad-request", Some("client"), Some("client")),
("400", "unknown", Some("client"), Some("client")),
("499", "unknown", Some("client"), Some("client")),
("500", "resource-constraint", Some("client"), Some("server")),
(
"500",
"internal-server-error",
Some("server"),
Some("server"),
),
("500", "unknown", Some("server"), Some("server")),
("503", "service-unavailable", Some("server"), Some("server")),
(
"530",
"partial-server-error",
Some("server"),
Some("server"),
),
("599", "unknown", Some("server"), Some("server")),
("600", "unknown", None, None),
] {
let node = error(code, Some(text));
assert_eq!(accept_class(&node).ok(), accept, "accept: {code}/{text}");
assert_eq!(subject_class(&node).ok(), subject, "subject: {code}/{text}");
}
}
#[test]
fn decimal_coercion_and_invalid_codes() {
for code in ["0304", "+304", "\t304tail", "\u{feff}304", "304.9", "304e7"] {
assert_eq!(
accept_class(&error(code, Some("already-exists"))).unwrap(),
"client"
);
}
for code in [
"",
"+",
"-304",
"0x130",
"\u{0085}304",
"∞",
"NaN",
"9999999999999999999999",
" + 304",
] {
assert!(
accept_class(&error(code, Some("already-exists"))).is_err(),
"{code:?}"
);
}
for key in ["code", "text"] {
let mut node = error("400", Some("bad-request"));
node.children[0].attrs.remove(key);
assert!(accept_class(&node).is_err());
assert!(subject_class(&node).is_err());
}
let mut node = error("400", Some("bad-request"));
node.children.push(node.children[0].clone());
assert!(accept_class(&node).is_err());
assert!(subject_class(&node).is_err());
}
#[test]
fn correlation_is_required_for_every_response_kind() {
let (subject, accept) = specs();
for node in [
result(vec![]),
result(vec![Node::new("membership_approval_request", &[], vec![])]),
error("400", Some("bad-request")),
error("500", Some("internal-server-error")),
] {
assert!(subject.parse_response(&node.as_ref()).is_err());
assert!(accept.parse_response(&node.as_ref()).is_err());
for key in ["id", "from", "type"] {
let mut invalid = node.clone();
invalid.attrs.remove(key);
assert!(subject_class(&invalid).is_err());
assert!(accept_class(&invalid).is_err());
invalid.attrs.insert(key.into(), "wrong".into());
assert!(subject_class(&invalid).is_err());
assert!(accept_class(&invalid).is_err());
}
let mut invalid = node.clone();
invalid.tag = "message".into();
assert!(subject_class(&invalid).is_err());
assert!(accept_class(&invalid).is_err());
for (id, to) in [
("wrong", "123@g.us"),
("req-1", "999@g.us"),
("", "123@g.us"),
("req-1", ""),
] {
assert!(
subject
.parse_response_with_request(&node.as_ref(), id, to)
.is_err()
);
assert!(
accept
.parse_response_with_request(&node.as_ref(), id, to)
.is_err()
);
}
}
// The actual wire request context controls correlation, not the spec's
// original target and not values fabricated from the received stanza.
let mut node = result(vec![]);
node.attrs.insert("from".into(), "999@g.us".into());
assert!(
subject
.parse_response_with_request(&node.as_ref(), "req-1", "999@g.us")
.is_ok()
);
assert!(
accept
.parse_response_with_request(&node.as_ref(), "req-1", "999@g.us")
.is_ok()
);
}
#[test]
fn optional_field_payload_and_source_fallback() {
let subject = specs().0;
let mut node = error("406", Some("not-acceptable"));
node.children[0].children = vec![Node::new(
"field",
&[("name", "subject"), ("reason", "too-long")],
vec![],
)];
let MakeSetSubjectRequestResponse::ClientError(value) = subject
.parse_response_with_request(&node.as_ref(), "req-1", "123@g.us")
.unwrap()
else {
panic!("client error required")
};
let Some(MakeSetSubjectRequestClientErrorErrorSetSubjectClientErrors::IQErrorNotAcceptable(
payload,
)) = value.error_set_subject_client_errors
else {
panic!("specific error required")
};
assert_eq!(
(
payload.code,
payload.text.as_str(),
payload.name.as_str(),
payload.reason.as_str()
),
(406, "not-acceptable", "subject", "too-long")
);
for children in [
vec![Node::new("field", &[("name", "subject")], vec![])],
vec![
Node::new("field", &[], vec![]),
Node::new("field", &[], vec![]),
],
] {
let mut node = error("406", Some("not-acceptable"));
node.children[0].children = children;
let MakeSetSubjectRequestResponse::ClientError(value) = subject
.parse_response_with_request(&node.as_ref(), "req-1", "123@g.us")
.unwrap()
else {
panic!("client error required")
};
let Some(
MakeSetSubjectRequestClientErrorErrorSetSubjectClientErrors::IQErrorFallbackClient(
payload,
),
) = value.error_set_subject_client_errors
else {
panic!("source disjunction must fall through")
};
assert_eq!(
(payload.code, payload.text.as_str()),
(406, "not-acceptable")
);
}
}
#[test]
fn binary_error_content_reaches_fallback() {
let subject = specs().0;
let mut response = error("406", Some("not-acceptable"));
response.children[0].bytes = Some(b"opaque".to_vec());
// The optionalChildWithTag path rejects Uint8Array before code/text.
// SetSubjectClientErrors then tries the 400..499 fallback, which succeeds.
let MakeSetSubjectRequestResponse::ClientError(value) = subject
.parse_response_with_request(&response.as_ref(), "req-1", "123@g.us")
.unwrap()
else {
panic!("client error required")
};
assert!(
matches!(
value.error_set_subject_client_errors,
Some(
MakeSetSubjectRequestClientErrorErrorSetSubjectClientErrors::IQErrorFallbackClient(
_
)
)
),
"binary error content must reach fallback: {value:?}"
);
}

#[test]
fn absent_and_empty_binary_error_content_remain_distinct() {
let subject = specs().0;
for binary in [None, Some(vec![]), Some(b"opaque".to_vec()), Some(vec![0xff])] {
let mut response = error("406", Some("not-acceptable"));
response.children[0].bytes = binary.clone();
let MakeSetSubjectRequestResponse::ClientError(value) = subject
.parse_response_with_request(&response.as_ref(), "req-1", "123@g.us")
.unwrap()
else {
panic!("client error required")
};
let specific = matches!(value.error_set_subject_client_errors,
Some(MakeSetSubjectRequestClientErrorErrorSetSubjectClientErrors::IQErrorNotAcceptable(_)));
let fallback = matches!(value.error_set_subject_client_errors,
Some(MakeSetSubjectRequestClientErrorErrorSetSubjectClientErrors::IQErrorFallbackClient(_)));
assert_eq!(specific, binary.is_none(), "{value:?}");
assert_eq!(fallback, binary.is_some(), "{value:?}");
}
// Parsers that inspect only attributes must still accept binary content.
for bytes in [vec![], b"opaque".to_vec(), vec![0xff]] {
let mut response = error("499", Some("unknown"));
response.children[0].bytes = Some(bytes);
assert_eq!(subject_class(&response).unwrap(), "client");
assert_eq!(accept_class(&response).unwrap(), "client");
}
}
Loading
Loading