diff --git a/docs/decisions/0002-xmpp-parser-validation.md b/docs/decisions/0002-xmpp-parser-validation.md
new file mode 100644
index 0000000..3042883
--- /dev/null
+++ b/docs/decisions/0002-xmpp-parser-validation.md
@@ -0,0 +1,100 @@
+# ADR 0002 parser remediation: bounded standalone XML ingress
+
+- Status: proposed; independent security review pending.
+- Task: S00-018 (remediation of S00-003).
+- Date: 2026-10-15.
+- Owner: auto-protocol-89fe6563092c (protocol).
+- Reviewer: not yet assigned.
+
+Input: reviewed scope/stack/SASL ADRs, S00-003 parser blocker and bounded probe,
+shipped Stanza parser/transport declarations and RFC 7395. Output: one isolated
+runnable ingress experiment in experiments/0002-xmpp-library/parser-validation.
+It does not select Stanza for the product, define production limits/shared
+contracts, change authentication, or replace remaining S00-003 evidence.
+
+The stock 12.22.1 transport keeps a StreamParser across WebSocket messages and
+converts payloads to text. The finite diagnostic reproduced a 1 MiB body, depth
+256 and 18 separate writes forming one 64 KiB body without errors. StreamParser
+has no supported resource-budget options. The standalone-frame obligation is
+[RFC 7395 §3.2/3.3.3](https://www.rfc-editor.org/rfc/rfc7395.html#section-3.3.3).
+Inference from the shipped transport: stock parser reassembly violates that
+obligation when each probe write represents a separate WebSocket message.
+The RFC does not prescribe our numerical limits.
+
+Propose a new public Client.transports constructor registration, selected alone
+through config. The public Transport interface lists connect/disconnect/restart/
+send; Client.connect instantiates the registered constructor with Client,
+StreamManagement and Registry. This is a supported shipped integration surface,
+not a private subclass override or ignored vendor patch. Exact shipped API:
+node_modules/stanza/Client.d.ts, index.d.ts, Client.js, jxt/index.d.ts,
+jxt/Parser.js and jxt/Registry.js. Upstream [Client source](https://github.com/legastero/stanza/blob/master/src/Client.ts)
+is supporting context; installed 12.22.1 declarations/behavior are authoritative
+for this experiment. Future stability is not promised.
+
+Before any JXT tree/import, require text and finite bytes, then use a new
+namespace-aware saxes XML 1.0 document parser and close it for completeness.
+Throw on malformed input or exceeded event counters, reject restricted constructs,
+and only then run the actual JXT.parse and Registry.import. Local ceilings:
+
+| Resource | Maximum, inclusive | Enforcement |
+| --- | --- | --- |
+| Encoded message | 4096 UTF-8 bytes | Unit precheck, bounded TextEncoder, before SAX |
+| Depth including root | 8 | SAX opentagstart |
+| Elements including root | 32 | SAX opentagstart |
+| Attributes including xmlns | 16 total/document | SAX attribute |
+| Attribute name+decoded value units | 1024 total/document | SAX attribute |
+| Element/attribute name units | 128 each | SAX event |
+| Decoded attribute value units | 256 each | SAX attribute |
+
+SAX itself stores tokens/namespaces before callbacks; their worst-case source
+is capped by bytes first. No handwritten XML security parser or regex syntax
+approximation is introduced. XML declarations, comments, PI and DTDs are
+conservatively rejected; CDATA/built-in/numeric entities work. No entity fetch
+or expansion extension is enabled. The entire document is validated before
+any earlier root can emit. Incomplete documents/multiple roots/binary messages
+are terminal and cannot be completed by subsequent messages. Close detaches
+callbacks, clears attempt stream/config state and rejects stale callbacks.
+Fresh connect creates a fresh transport. No payload is logged or raw-evented.
+
+Alternatives rejected: post-import counters (too late), stock persistent parser
+(no budgets/framing), regex or custom security parser (incorrect/unvetted),
+DOMParser (tree allocated before structural checks), private source patches
+(unsupported integration). A strict event parser plus byte ceiling provides a
+bounded preflight without authoring XML syntax. Double parsing retains actual
+candidate behavior, with bounded cost; complete normalization equivalence is
+not established and is not claimed.
+
+New exact dependencies: saxes 6.0.0 (ISC), xmlchars 2.2.0 (MIT); Stanza remains
+12.22.1 (MIT). Ten exact installed packages are locked with HTTPS registry URLs,
+SHA512 integrity, full license inventory and notices. Saxes published tarball
+omits LICENSE; preserved upstream [v6.0.0 license](https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE)
+includes ISC lineage and an historical MIT notice. Public API/browser constraints
+were checked against [versioned saxes README](https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/README.md)
+and installed saxes.d.ts/source, not assumptions about a parser's strict mode.
+Sources inspected 2026-10-15. Saxes has an older release; maintenance/advisory
+clearance is not inferred from version pinning. README conformance claims are
+upstream claims; our evidence is the bounded tests.
+
+Verification: pinned Node 24.21.0/npm 11.19.0, fresh-cache npm ci, 55 integrated
+tests passing; aggregate boundary document and Unicode boundary actually
+import; all original failure categories and adversarial cases reject without
+stanza emission/late revival. Registry tracing calls the real import. All 340
+installed Stanza files byte-match the original candidate; no vendor edits.
+make check passes 72 tooling tests (foundation only). Commands and setup details
+are in the experiment README and state/evidence/S00-018-auto-protocol-89fe6563092c.md.
+
+Browser use requires a CommonJS-capable bundler, native WebSocket/URL/TextEncoder
+and private fields. Default uses browser WebSocket, fixture injection captures
+socket effects only; actual Client.connect and its queue/registry/parser execute.
+No browser build/runtime, actual WSS/TLS handshake, independent server/client
+interoperability or exhaustion benchmark was run. Byte checks begin after
+browser message allocation, and do not bound aggregate traffic/Client queues or
+downstream work. This minimal transport does not implement all error reporting,
+close negotiation, redirect, keepalive, language inheritance or XMPP sequencing.
+No TLS/CORS bypass, invented channel binding, crypto primitive, production policy
+or shared contract is added. ACC-004/NET-003/NET-004 remain obligations of the
+original selection/product work; existing accepted SASL experiment is unchanged.
+
+Independent security reviewer must inspect enforcement order/API integration,
+repeat clean install/tests/license/vendor checks and record acceptance before
+this ADR or S00-018 is accepted. Coordinator alone commits/launches further work.
diff --git a/experiments/0002-xmpp-library/parser-validation/.npmrc b/experiments/0002-xmpp-library/parser-validation/.npmrc
new file mode 100644
index 0000000..d253cac
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/.npmrc
@@ -0,0 +1,3 @@
+engine-strict=true
+strict-ssl=true
+registry=https://registry.npmjs.org/
diff --git a/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md b/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md
new file mode 100644
index 0000000..a621039
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md
@@ -0,0 +1,16 @@
+# Exact dependency license inventory
+
+Inspected 2026-10-15. Node 24.21.0 / npm 11.19.0. Saxes tarball omits its license text: preserved upstream v6.0.0 LICENSE in licenses/saxes-6.0.0-LICENSE, retrieved via verified HTTPS. Other texts are shipped installed files. No advisory/provenance clearance is claimed.
+
+| Package | Version | SPDX | License/notice source |
+| --- | --- | --- | --- |
+| @types/node | 22.20.5 | MIT | LICENSE |
+| @types/punycode | 2.1.4 | MIT | LICENSE |
+| @types/ws | 8.18.2 | MIT | LICENSE |
+| punycode | 2.3.1 | MIT | LICENSE-MIT.txt |
+| saxes | 6.0.0 | ISC | saxes-6.0.0-LICENSE |
+| sdp | 3.2.2 | MIT | LICENSE |
+| stanza | 12.22.1 | MIT | LICENSE.md, NOTICE.md |
+| undici-types | 6.21.0 | MIT | LICENSE |
+| ws | 8.22.0 | MIT | LICENSE |
+| xmlchars | 2.2.0 | MIT | LICENSE |
diff --git a/experiments/0002-xmpp-library/parser-validation/README.md b/experiments/0002-xmpp-library/parser-validation/README.md
new file mode 100644
index 0000000..9b65e86
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/README.md
@@ -0,0 +1,96 @@
+# Bounded standalone XML ingress experiment (S00-018)
+
+This fixture experiment uses real Stanza 12.22.1 Client dispatch, JXT.parse and
+registry.import, preceded by saxes 6.0.0 XML 1.0 namespace-aware validation.
+It adds a constructor to the public Client.transports registry and selects only
+that transport via configuration. It does not use the stock persistent parser,
+subclass private transport fields, intercept already imported events, or patch
+vendor files. It is not a production transport or a library selection.
+
+From project root, reuse the reviewed isolated Node runtime (macOS ARM64):
+
+```sh
+export PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH
+npm ci --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-review-cache --fetch-retries=0 --fetch-timeout=15000
+npm test --prefix experiments/0002-xmpp-library/parser-validation
+python3 experiments/0002-xmpp-library/parser-validation/check-dependencies.py
+node experiments/0002-xmpp-library/parser-probe/reproduce.mjs
+```
+
+The last command reproduces stock defects; exit 0 is NOT safe parsing.
+The dependency checker also requires the original candidate already installed in
+experiments/0002-xmpp-library/node_modules for byte comparison. It checks all 340
+Stanza files and ten exact locked installed package licenses/integrities. The
+saxes tarball omits LICENSE: the upstream v6.0.0 license is preserved locally,
+with source https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE.
+
+## Enforcement order and local policy
+
+1. Require primitive string. Reject Blob/ArrayBuffer/typed arrays before coercion.
+2. Reject more than 4096 UTF-16 units without encoding; then reject more than
+ 4096 UTF-8 bytes using TextEncoder. The temporary encoding buffer is bounded
+ by the unit check (at most 12288 bytes for any input); it is not an unbounded
+ copy. First character must be `<` as required by RFC 7395.
+3. New saxes parser per message, fragment=false, xmlns=true, XML 1.0 forced,
+ no inherited namespace resolver. Throw on the first error; close() checks
+ document completion. No tree is built during this phase.
+4. At opentagstart reject depth >8 (including root), elements >32, name >128
+ UTF-16 units. At attribute reject total attributes >16 (including xmlns),
+ name >128, decoded value >256, or aggregate decoded attribute name+value
+ units >1024. Checks occur before JXT tree construction/import. Saxes has
+ already stored one completed attribute at its callback; lexical tokens,
+ namespace tables and DTD buffers before callbacks are bounded by 4096 input
+ bytes. No claim of allocation-free SAX processing is made.
+5. Reject DTDs, comments, processing instructions and XML declarations. Only
+ built-in/numeric entities are available; no DTD fetching/entity expansion is
+ enabled. CDATA is accepted. Declaration rejection is local conservative
+ policy; RFC 7395 discourages but does not forbid declarations.
+6. Only after complete validation call real JXT.parse and registry.import. An
+ unknown top-level root or candidate parser/import error closes the attempt.
+ Parsed output is delivered through Client stream:data only after import
+ succeeds. No invalid document's earlier root is emitted.
+7. On rejection close the socket, detach all four callbacks, clear config and
+ stream references before emitting a payload-free error code. Every old
+ captured callback checks the terminal flag. Parsers/trees are synchronous
+ function locals, never retained between messages. A fresh Client.connect
+ creates another transport; restart on a failed transport cannot revive it.
+
+The combined boundary fixture hits 4096 bytes, depth 8, 32 elements,
+16 attributes and 1024 attribute units simultaneously and imports a real body.
+Separate tests cover name/value equality, multibyte byte equality, one-over
+bounds, stock 1 MiB/depth-256/18-message cases, malformed tails, incomplete XML,
+binary, duplicate attributes/namespaces/entities/invalid characters, callback
+replay, fresh connect, stream open/close/restart and outgoing registry export.
+Tracing registry.import calls the real implementation; it never fakes success.
+FixtureSocket replaces only browser socket effects; tests use actual Client
+connect/transport selection and stanza queue events.
+
+## Constraints
+
+The default socket constructor is globalThis.WebSocket; browser use requires a
+CommonJS-capable bundler for Stanza/saxes/xmlchars, native WebSocket, URL and
+TextEncoder plus support for private class fields. The injected fixture socket
+is explicitly separate and never chosen by the default path. Only explicit
+wss URLs and the xmpp subprotocol are allowed. TLS/CORS/binding are unchanged;
+no real WSS handshake is exercised here. Browser build/runtime and selected
+browser versions are NOT verified. Node tests run 24.21.0/npm 11.19.0.
+
+Limits protect application parsing after the browser delivers a complete message.
+They cannot prevent the browser/network stack from allocating a huge inbound
+message, nor bound total traffic, Client queue growth, CPU rate, reconnect
+policy or downstream extension work. Those need separate product/provider
+policies. The experiment closes locally on violations rather than implementing
+full XMPP stream-error reporting/close negotiation/redirect/keepalive behavior.
+It does not enforce complete XMPP stream sequencing or preserve inherited stream
+language. JXT interpretation/normalization is retained; SAX and JXT equivalence
+for all accepted XML forms is not established. No auth/crypto changes, complete
+NET-003 policy, OMEMO, independent server/client interoperability, exhaustion
+benchmark, advisory/provenance audit, or release acceptance is claimed.
+
+All ten package license texts/notices are in THIRD_PARTY_NOTICES.md and exact
+versions in LICENSE_INVENTORY.md/package-lock.json. Saxes is a mature existing
+strict parser with documented XML/namespace conformance and public event APIs;
+6.0.0 is an older release, and no ongoing-maintenance SLA is assumed. Its
+absence of built-in token limits is why the mandatory byte precheck comes first.
+See proposed docs/decisions/0002-xmpp-parser-validation.md. Independent security
+review is required; S00-003 remains a separate assignment after reviewed done.
diff --git a/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md b/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md
new file mode 100644
index 0000000..fe9576e
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md
@@ -0,0 +1,637 @@
+# Third-party notices
+
+Verbatim license/notice texts for this isolated experiment.
+
+## @types/node 22.20.5 — LICENSE
+
+MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
+
+## @types/punycode 2.1.4 — LICENSE
+
+MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
+
+## @types/ws 8.18.2 — LICENSE
+
+MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
+
+## punycode 2.3.1 — LICENSE-MIT.txt
+
+Copyright Mathias Bynens
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## saxes 6.0.0 — saxes-6.0.0-LICENSE
+
+The ISC License
+
+Copyright (c) Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+====
+
+The following license is the one that governed sax, from which saxes
+was forked. Isaac Schlueter is not *directly* involved with saxes so
+don't go bugging him for saxes issues.
+
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+====
+
+`String.fromCodePoint` by Mathias Bynens is no longer used, but it can
+still be found in old commits. It was once used according to terms of
+MIT License, as follows:
+
+ Copyright Mathias Bynens
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## sdp 3.2.2 — LICENSE
+
+Copyright (c) 2017 Philipp Hancke
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## stanza 12.22.1 — LICENSE.md
+
+The MIT License (MIT)
+
+Copyright © 2013-2019 Lance Stout
+
+Copyright © 2013-2019 &yet
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## stanza 12.22.1 — NOTICE.md
+
+# StanzaJS
+
+Copyright © 2013-2019 Lance Stout
+
+Copyright © 2013-2019 &yet
+
+Portions of this code were adapted from existing projects:
+
+## SASL
+
+- saslmechanisms
+- sasl-anonymous
+- sasl-digest-md5
+- sasl-external
+- sasl-plain
+- sasl-x-oauth2
+
+(The MIT License)
+
+Copyright © 2012-2013 Jared Hanson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## ltx
+
+Copyright © 2010 Stephan Maka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+## xhtml-im.js
+
+The MIT License (MIT)
+
+Copyright © 2013 Waqas Hussain
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## wildemitter
+
+The MIT License (MIT)
+
+Copyright © 2012 Henrik Joreteg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## hash-base
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Kirill Fomichev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+## cipher-base
+
+The MIT License (MIT)
+
+Copyright (c) 2017 crypto-browserify contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## sha.js
+
+Copyright (c) 2013-2018 sha.js contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Copyright (c) 1998 - 2009, Paul Johnston & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## md5.js
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Kirill Fomichev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+## create-hash
+
+The MIT License (MIT)
+
+Copyright (c) 2017 crypto-browserify contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## create-hmac
+
+The MIT License (MIT)
+
+Copyright (c) 2017 crypto-browserify contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## randombytes
+
+MIT License
+
+Copyright (c) 2017 crypto-browserify
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## uuid
+
+The MIT License (MIT)
+
+Copyright (c) 2010-2016 Robert Kieffer and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## strict-event-emitter-types
+
+ISC License (ISC)
+
+Copyright 2018 Brian Terlson
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+## capsdb
+
+capsdb - A collection of XMPP caps with hashes
+
+Written in 2015 by Thijs Alkemade me@thijsalkema.de
+
+To the extent possible under law, the author(s) have dedicated all copyright and
+related and neighboring rights to this software to the public domain worldwide.
+This software is distributed without any warranty.
+
+You should have received a copy of the CC0 Public Domain Dedication along with
+this software. If not, see http://creativecommons.org/publicdomain/zero/1.0/.
+
+## undici-types 6.21.0 — LICENSE
+
+MIT License
+
+Copyright (c) Matteo Collina and Undici contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+## ws 8.22.0 — LICENSE
+
+Copyright (c) 2011 Einar Otto Stangvik
+Copyright (c) 2013 Arnout Kazemier and contributors
+Copyright (c) 2016 Luigi Pinca and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## xmlchars 2.2.0 — LICENSE
+
+Copyright Louis-Dominique Dubeau and contributors to xmlchars
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs b/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs
new file mode 100644
index 0000000..8dcfee0
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs
@@ -0,0 +1,146 @@
+import {SaxesParser} from 'saxes';
+import jxt from 'stanza/jxt/index.js';
+
+// Experiment policy only. The byte ceiling bounds SAX token/namespace storage
+// even before callbacks can inspect a completed name or attribute value.
+export const LIMITS = Object.freeze({bytes: 4096, depth: 8, elements: 32,
+ attributes: 16, attributeChars: 1024, nameChars: 128, valueChars: 256});
+class IngressError extends Error {
+ constructor(code) { super(code); this.code = code; }
+}
+const fail = code => { throw new IngressError(code); };
+
+export function validateMessage(data) {
+ if (typeof data !== 'string') fail('text-required');
+ // Avoid encoding an attacker-sized string. UTF-8 bytes >= UTF-16 units for
+ // valid XML; isolated surrogates are subsequently rejected by saxes.
+ if (data.length > LIMITS.bytes) fail('bytes');
+ if (new TextEncoder().encode(data).length > LIMITS.bytes) fail('bytes');
+ if (!data.startsWith('<')) fail('framing');
+ let depth = 0, elements = 0, attributes = 0, attributeChars = 0, ended = false;
+ const parser = new SaxesParser({xmlns: true, fragment: false,
+ defaultXMLVersion: '1.0', forceXMLVersion: true});
+ parser.on('error', () => fail('invalid-xml'));
+ for (const event of ['doctype', 'comment', 'processinginstruction', 'xmldecl']) {
+ parser.on(event, () => fail('restricted-xml'));
+ }
+ parser.on('opentagstart', tag => {
+ if (++depth > LIMITS.depth) fail('depth');
+ if (++elements > LIMITS.elements) fail('elements');
+ if (tag.name.length > LIMITS.nameChars) fail('name');
+ });
+ parser.on('attribute', attr => {
+ if (++attributes > LIMITS.attributes) fail('attributes');
+ if (attr.name.length > LIMITS.nameChars) fail('name');
+ if (attr.value.length > LIMITS.valueChars) fail('attribute-value');
+ attributeChars += attr.name.length + attr.value.length;
+ if (attributeChars > LIMITS.attributeChars) fail('attribute-chars');
+ });
+ parser.on('closetag', () => { depth--; });
+ parser.on('end', () => { ended = true; });
+ // close() detects unfinished XML. Throwing from callbacks aborts immediately;
+ // there is no recovery listener, DTD fetch, custom entity or tree construction.
+ parser.write(data).close();
+ if (!ended || depth !== 0 || elements === 0) fail('framing');
+}
+
+export function importMessage(data, registry, config) {
+ validateMessage(data);
+ // Only bounded, complete documents reach the actual candidate parser/import.
+ const xml = jxt.parse(data, {allowComments: false});
+ if (!xml) fail('invalid-xml');
+ const kind = registry.getImportKey(xml);
+ const stanza = registry.import(xml, {lang: config.lang,
+ acceptLanguages: config.acceptLanguages});
+ if (!kind || !stanza) fail('unknown-root');
+ return {kind, stanza};
+}
+
+// Public Transport interface and Client.transports constructor registry.
+// Socket injection is solely for finite fixtures; default is browser WebSocket.
+export function installBoundedTransport(client, Socket = globalThis.WebSocket) {
+ class BoundedTransport {
+ #client; #registry; #sm; #socket; #config; #closed = true;
+ hasStream = false;
+ stream;
+ constructor(agent, sm, registry) {
+ this.#client = agent; this.#sm = sm; this.#registry = registry;
+ }
+ connect(config) {
+ if (this.#socket || !this.#closed) throw new Error('Fresh transport required');
+ if (new URL(config.url).protocol !== 'wss:') throw new Error('WSS required');
+ this.#config = config;
+ this.#closed = false;
+ const socket = this.#socket = new Socket(config.url, 'xmpp');
+ socket.onopen = () => {
+ if (this.#closed) return;
+ if (socket.protocol !== 'xmpp') return this.#reject('subprotocol');
+ this.#sm.started = false;
+ this.#client.emit('connected');
+ this.restart();
+ };
+ socket.onmessage = event => {
+ if (this.#closed) return;
+ let parsed;
+ try { parsed = importMessage(event.data, this.#registry, this.#config); }
+ catch (error) { return this.#reject(error instanceof IngressError ? error.code : 'invalid-xml'); }
+ // No raw payload event/log or async parser continuation escapes ingress.
+ if (parsed.kind === 'stream') {
+ if (parsed.stanza.action === 'open') {
+ this.hasStream = true;
+ this.stream = parsed.stanza;
+ this.#client.emit('stream:start', parsed.stanza);
+ } else {
+ this.#client.emit('stream:end');
+ this.disconnect(false);
+ }
+ } else {
+ this.#client.emit('stream:data', parsed.stanza, parsed.kind);
+ }
+ };
+ socket.onerror = () => this.#reject('socket-error');
+ socket.onclose = () => this.disconnect(false);
+ }
+ #reject(code) {
+ if (this.#closed) return;
+ // Close/clear before notifying consumers; a reentrant callback cannot
+ // revive the attempt. Never expose parser error text containing input.
+ this.disconnect(false);
+ this.#client.emit('ingress:rejected', code);
+ }
+ disconnect(clean = true) {
+ if (this.#closed) return;
+ this.#closed = true;
+ const socket = this.#socket;
+ this.#socket = undefined;
+ if (socket) {
+ socket.onopen = socket.onmessage = socket.onerror = socket.onclose = null;
+ if (clean && this.hasStream && socket.readyState === 1) {
+ socket.send(this.#registry.export('stream', {action: 'close'}).toString());
+ }
+ socket.close();
+ }
+ this.hasStream = false;
+ this.stream = undefined;
+ this.#config = undefined;
+ if (this.#client.transport === this) this.#client.emit('--transport-disconnected');
+ }
+ restart() {
+ if (this.#closed) return;
+ this.hasStream = false;
+ this.stream = undefined;
+ this.#write(this.#registry.export('stream', {action: 'open',
+ lang: this.#config.lang, to: this.#config.server, version: '1.0'}).toString());
+ }
+ #write(data) {
+ if (this.#closed || this.#socket?.readyState !== 1) throw new Error('Socket closed');
+ this.#socket.send(data);
+ }
+ async send(name, data) {
+ const xml = this.#registry.export(name, data);
+ if (!xml) throw new Error('Unknown outgoing stanza');
+ this.#write(xml.toString());
+ }
+ }
+ client.transports['bounded-websocket'] = BoundedTransport;
+}
diff --git a/experiments/0002-xmpp-library/parser-validation/check-dependencies.py b/experiments/0002-xmpp-library/parser-validation/check-dependencies.py
new file mode 100644
index 0000000..de5bf4b
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/check-dependencies.py
@@ -0,0 +1,42 @@
+"""Check exact installed graph, preserved notices, and unchanged candidate files."""
+import hashlib
+import json
+from pathlib import Path
+
+root = Path(__file__).resolve().parent
+lock = json.loads((root / 'package-lock.json').read_text())
+manifest = json.loads((root / 'package.json').read_text())
+assert manifest['engines'] == {'node': '24.21.0', 'npm': '11.19.0'}
+assert manifest['dependencies'] == {'saxes': '6.0.0', 'stanza': '12.22.1', 'xmlchars': '2.2.0'}
+notices = (root / 'THIRD_PARTY_NOTICES.md').read_text()
+inventory = (root / 'LICENSE_INVENTORY.md').read_text()
+count = 0
+for key, entry in lock['packages'].items():
+ if not key:
+ continue
+ count += 1
+ assert entry['resolved'].startswith('https://registry.npmjs.org/')
+ assert entry['integrity'].startswith('sha512-')
+ installed = root / key
+ meta = json.loads((installed / 'package.json').read_text())
+ assert meta['version'] == entry['version']
+ assert meta['license'] == entry['license']
+ assert f"| {meta['name']} | {meta['version']} | {meta['license']} |" in inventory
+ files = [p for p in installed.iterdir() if p.is_file() and
+ (p.name.lower().startswith('license') or p.name.lower().startswith('notice'))]
+ if meta['name'] == 'saxes':
+ files = [root / 'licenses/saxes-6.0.0-LICENSE']
+ assert files
+ for path in files:
+ assert path.read_text().strip() in notices
+assert count == 10
+# Independent previously installed candidate; no ignored vendor patches.
+candidate = root / 'node_modules/stanza'
+original = root.parent / 'node_modules/stanza'
+files = [p for p in candidate.rglob('*') if p.is_file()]
+assert files
+for path in files:
+ assert path.read_bytes() == (original / path.relative_to(candidate)).read_bytes(), path
+print(f'{count} exact dependency licenses/integrities; {len(files)} unchanged Stanza files')
+for name in ['package-lock.json', 'bounded-ingress.mjs', 'licenses/saxes-6.0.0-LICENSE']:
+ print(f'{name} SHA256 {hashlib.sha256((root / name).read_bytes()).hexdigest()}')
diff --git a/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE b/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE
new file mode 100644
index 0000000..187f3ba
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE
@@ -0,0 +1,64 @@
+The ISC License
+
+Copyright (c) Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+====
+
+The following license is the one that governed sax, from which saxes
+was forked. Isaac Schlueter is not *directly* involved with saxes so
+don't go bugging him for saxes issues.
+
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+====
+
+`String.fromCodePoint` by Mathias Bynens is no longer used, but it can
+still be found in old commits. It was once used according to terms of
+MIT License, as follows:
+
+ Copyright Mathias Bynens
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/experiments/0002-xmpp-library/parser-validation/package-lock.json b/experiments/0002-xmpp-library/parser-validation/package-lock.json
new file mode 100644
index 0000000..9346ad3
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/package-lock.json
@@ -0,0 +1,119 @@
+{
+ "name": "luma-bounded-xml-ingress-experiment",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "luma-bounded-xml-ingress-experiment",
+ "version": "0.0.0",
+ "dependencies": {
+ "saxes": "6.0.0",
+ "stanza": "12.22.1",
+ "xmlchars": "2.2.0"
+ },
+ "engines": {
+ "node": "24.21.0",
+ "npm": "11.19.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
+ "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/punycode": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.4.tgz",
+ "integrity": "sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.2",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz",
+ "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/sdp": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
+ "integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
+ "license": "MIT"
+ },
+ "node_modules/stanza": {
+ "version": "12.22.1",
+ "resolved": "https://registry.npmjs.org/stanza/-/stanza-12.22.1.tgz",
+ "integrity": "sha512-WwoSl3u5cCEAbed5ao+yt8ByH6AsBJcPDY0gax3JGQkBX/7wEZ2oxu9nMJ7O1tm8slDyH4FBYIpeluHrT+g/LA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^22.0.0",
+ "@types/punycode": "^2.1.0",
+ "@types/ws": "^8.18.1",
+ "punycode": "^2.1.1",
+ "sdp": "^3.0.2",
+ "ws": "^8.18.2"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/ws": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
+ "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/experiments/0002-xmpp-library/parser-validation/package.json b/experiments/0002-xmpp-library/parser-validation/package.json
new file mode 100644
index 0000000..504f9fb
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "luma-bounded-xml-ingress-experiment",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "engines": {"node": "24.21.0", "npm": "11.19.0"},
+ "packageManager": "npm@11.19.0",
+ "scripts": {"test": "node --test test/*.test.mjs"},
+ "dependencies": {"saxes": "6.0.0", "stanza": "12.22.1", "xmlchars": "2.2.0"}
+}
diff --git a/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs b/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs
new file mode 100644
index 0000000..944a444
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs
@@ -0,0 +1,68 @@
+export const opening = '';
+export const closing = '';
+export const message = text => opening + text + closing;
+export const valid = message('finite fixture');
+export const deep = n => '' + ''.repeat(n - 1) +
+ ''.repeat(n - 1) + '';
+export const elements = n => '' + ''.repeat(n - 1) + '';
+export const attributes = n => ' ` a${i}="x"`).join('') + '/>';
+export function boundary() {
+ // 16 attributes / 1024 name+value UTF-16 units including xmlns.
+ let attrs = ' xmlns="jabber:client"';
+ let chars = 'xmlns'.length + 'jabber:client'.length;
+ for (let i = 0; i < 15; i++) {
+ const name = `a${i}`;
+ const value = 'v'.repeat(i === 14 ? 1024 - chars - name.length : 60);
+ attrs += ` ${name}="${value}"`;
+ chars += name.length + value.length;
+ }
+ // Root + body + chain of 7 + 23 siblings = 32 elements; depth 8.
+ const before = ``;
+ const after = '' + ''.repeat(7) + ''.repeat(7) + ''.repeat(23) + '';
+ return {xml: before + 'z'.repeat(4096 - before.length - after.length) + after,
+ body: 'z'.repeat(4096 - before.length - after.length)};
+}
+export const invalid = [
+ ['large stock body', message('b'.repeat(1048576)), 'bytes'],
+ ['deep stock XML', deep(257), 'depth'],
+ ['unfinished opening', opening, 'invalid-xml'],
+ ['unfinished attribute', '', 'invalid-xml'],
+ ['depth over', deep(9), 'depth'],
+ ['elements over', elements(33), 'elements'],
+ ['attributes over', attributes(17), 'attributes'],
+ ['attribute value over', ``, 'attribute-value'],
+ ['attribute chars over', ' `a${i}="${'x'.repeat(256)}"`).join(' ') + '/>', 'attribute-chars'],
+ ['oversized name', `<${'x'.repeat(129)}/>`, 'name'],
+ ['oversized attribute name', ``, 'name'],
+ ['oversized unfinished token', '<' + 'x'.repeat(4096), 'bytes'],
+ ['UTF-8 bytes over with bounded units', message('🦉'.repeat(1100)), 'bytes'],
+ ['binary buffer', new Uint8Array([60, 120, 47, 62]), 'text-required'],
+ ['binary ArrayBuffer', new ArrayBuffer(4), 'text-required'],
+ ['binary Blob', new Blob([valid]), 'text-required'],
+ ['boxed string', new String(valid), 'text-required'],
+ ['comment', '' + valid, 'restricted-xml'],
+ ['DTD internal entity', ']>' + message('&x;'), 'restricted-xml'],
+ ['external DTD', '' + valid, 'restricted-xml'],
+ ['processing instruction', '' + valid, 'restricted-xml'],
+ ['XML declaration', '' + valid, 'restricted-xml'],
+ ['duplicate attribute', '', 'invalid-xml'],
+ ['duplicate expanded attribute', '', 'invalid-xml'],
+ ['unbound namespace', '', 'invalid-xml'],
+ ['illegal namespace binding', '', 'invalid-xml'],
+ ['unknown entity', message('&unknown;'), 'invalid-xml'],
+ ['illegal control', message('\u0000'), 'invalid-xml'],
+ ['unpaired surrogate', message('\ud800'), 'invalid-xml'],
+ ['invalid char reference', message(''), 'invalid-xml'],
+ ['unknown root', '', 'unknown-root']
+];
diff --git a/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs b/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs
new file mode 100644
index 0000000..d59cb13
--- /dev/null
+++ b/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs
@@ -0,0 +1,151 @@
+import assert from 'node:assert/strict';
+import {test} from 'node:test';
+import clientModule from 'stanza/Client.js';
+import {installBoundedTransport, validateMessage, LIMITS} from '../bounded-ingress.mjs';
+import {opening, closing, message, valid, deep, elements, attributes, boundary, invalid} from './fixtures.mjs';
+const Client = clientModule.default;
+const tick = () => new Promise(resolve => setImmediate(resolve));
+
+async function harness() {
+ const sockets = [];
+ class FixtureSocket {
+ readyState = 1; protocol = 'xmpp'; sent = []; closes = 0;
+ constructor(url, protocol) { this.url = url; this.requestedProtocol = protocol; sockets.push(this); }
+ send(data) { this.sent.push(data); }
+ close() { this.closes++; this.readyState = 3; }
+ }
+ const client = new Client({autoReconnect: false, server: 'example.invalid'});
+ installBoundedTransport(client, FixtureSocket);
+ client.updateConfig({transports: {'bounded-websocket': 'wss://example.invalid/xmpp'},
+ transportPreferenceOrder: ['bounded-websocket']});
+ const received = [], errors = [], raw = [], stanzas = [], starts = [];
+ client.on('stream:data', (stanza, kind) => received.push({stanza, kind}));
+ client.on('stanza', stanza => stanzas.push(stanza));
+ client.on('stream:start', stanza => starts.push(stanza));
+ client.on('ingress:rejected', code => errors.push(code));
+ client.on('raw', (...args) => raw.push(args));
+ // Tracing calls the real registry.import; proves rejection before import.
+ let imports = 0;
+ const original = client.stanzas.import.bind(client.stanzas);
+ client.stanzas.import = (...args) => { imports++; return original(...args); };
+ await client.connect();
+ const socket = sockets.at(-1);
+ socket.onopen();
+ return {client, sockets, socket, received, errors, raw, stanzas, starts,
+ imports: () => imports, finish: async () => {client.transport?.disconnect(false); await tick();}};
+}
+
+for (const [name, xml, code] of invalid) {
+ test(`terminal rejection: ${name}`, async () => {
+ const h = await harness();
+ const late = h.socket.onmessage;
+ late({data: xml});
+ assert.deepEqual(h.errors, [code]);
+ assert.equal(h.socket.closes, 1);
+ assert.equal(h.socket.onmessage, null);
+ assert.equal(h.received.length, 0);
+ assert.equal(h.starts.length, 0);
+ if (code !== 'unknown-root') assert.equal(h.imports(), 0);
+ // Old captured handlers and subsequent messages cannot revive an attempt.
+ late({data: closing}); late({data: valid});
+ await tick();
+ assert.equal(h.stanzas.length, 0);
+ assert.equal(h.received.length, 0);
+ assert.deepEqual(h.errors, [code]);
+ assert.equal(h.client.transport, undefined);
+ assert.equal(h.raw.length, 0);
+ await h.finish();
+ });
+}
+
+for (const [name, xml, body] of [
+ ['all aggregate limits equal', boundary().xml, boundary().body],
+ ['depth equal', deep(LIMITS.depth)],
+ ['element count equal', elements(LIMITS.elements)],
+ ['attribute count equal', attributes(LIMITS.attributes)],
+ ['attribute value equal', ``],
+ ['name equal', `<${'x'.repeat(128)}/>`],
+ ['UTF-8 byte equality', message('🦉'.repeat(1000) + 'a'.repeat(4096 - message('').length - 4000)), '🦉'.repeat(1000) + 'a'.repeat(4096 - message('').length - 4000)],
+ ['escaped text and CDATA', message('<&]]>'), '<&'],
+ ['prefixed namespace', 'namespace', 'namespace']
+]) {
+ test(`actual parser/import accepts ${name}`, async () => {
+ const h = await harness();
+ h.socket.onmessage({data: xml});
+ await tick();
+ assert.deepEqual(h.errors, []);
+ assert.equal(h.received.length, 1);
+ assert.equal(h.received[0].kind, 'message');
+ if (body !== undefined) assert.equal(h.received[0].stanza.body, body);
+ assert.equal(h.stanzas.length, 1);
+ assert.ok(h.imports() > 0);
+ await h.finish();
+ });
+}
+
+test('bytes one over and UTF-8 one over reject before import', async () => {
+ for (const xml of [boundary().xml + ' ', message('🦉'.repeat(1000) + 'a'.repeat(4097 - message('').length - 4000))]) {
+ const h = await harness(); h.socket.onmessage({data: xml});
+ assert.deepEqual(h.errors, ['bytes']); assert.equal(h.imports(), 0);
+ await h.finish();
+ }
+});
+
+test('stock 18-message partial document never accumulates or revives; fresh connect works', async () => {
+ const h = await harness(); const old = h.socket.onmessage;
+ old({data: opening});
+ for (let i = 0; i < 16; i++) old({data: 'a'.repeat(4096)});
+ old({data: closing});
+ await tick();
+ assert.equal(h.received.length, 0); assert.equal(h.imports(), 0);
+ assert.deepEqual(h.errors, ['invalid-xml']);
+ await h.client.connect(); const fresh = h.sockets.at(-1); fresh.onopen();
+ old({data: valid}); assert.equal(h.received.length, 0);
+ fresh.onmessage({data: valid}); await tick();
+ assert.equal(h.received.length, 1); assert.equal(h.stanzas.length, 1);
+ assert.equal(h.received[0].stanza.body, 'finite fixture');
+ await h.finish();
+});
+
+test('successive complete documents have no shared namespace/parser state', async () => {
+ const h = await harness();
+ h.socket.onmessage({data: valid}); h.socket.onmessage({data: valid}); await tick();
+ assert.equal(h.stanzas.length, 2);
+ h.socket.onmessage({data: ''}); await tick();
+ assert.deepEqual(h.errors, ['invalid-xml']); assert.equal(h.stanzas.length, 2);
+ await h.finish();
+});
+
+test('open, restart, message, outgoing export and close use actual registry', async () => {
+ const h = await harness();
+ h.socket.onmessage({data: ''});
+ assert.equal(h.client.transport.hasStream, true); assert.equal(h.starts.length, 1);
+ h.client.transport.restart(); assert.equal(h.client.transport.hasStream, false);
+ await h.client.transport.send('message', {body: 'outgoing fixture'});
+ assert.ok(h.socket.sent.at(-1).includes('outgoing fixture'));
+ h.socket.onmessage({data: ''});
+ await tick(); assert.equal(h.socket.closes, 1); assert.equal(h.client.transport, undefined);
+ assert.equal(h.errors.length, 0);
+ await h.finish();
+});
+
+test('failed subprotocol, socket error, clean disconnect and stale callbacks are terminal', async () => {
+ for (const mode of ['subprotocol', 'error', 'clean']) {
+ const h = await harness(); const old = h.socket.onmessage;
+ if (mode === 'subprotocol') {h.socket.protocol = 'wrong'; h.socket.onopen();}
+ if (mode === 'error') h.socket.onerror(new Error('fixture'));
+ if (mode === 'clean') {
+ h.socket.onmessage({data: ''});
+ h.client.transport.disconnect(); assert.ok(h.socket.sent.at(-1).includes('close'));
+ }
+ old({data: valid}); await tick();
+ assert.equal(h.received.length, 0); assert.equal(h.socket.closes, 1);
+ await h.finish();
+ }
+});
+
+test('SAX framing validation retains no attempt object', () => {
+ assert.throws(() => validateMessage(opening));
+ assert.throws(() => validateMessage(closing));
+ assert.doesNotThrow(() => validateMessage(valid));
+});
diff --git a/planning/TASK_MATRIX.md b/planning/TASK_MATRIX.md
index dba3ab9..0ae2aae 100644
--- a/planning/TASK_MATRIX.md
+++ b/planning/TASK_MATRIX.md
@@ -1,6 +1,6 @@
# Матрица задач
-Generated; state revision **43**. Canonical status: state/tasks.json.
+Generated; state revision **45**. Canonical status: state/tasks.json.
| ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача |
| --- | --- | --- | --- | --- | --- | --- | --- |
@@ -20,7 +20,7 @@ Generated; state revision **43**. Canonical status: state/tasks.json.
| [S00-015](tasks/S00-015.md) | S00 | ci-cd | qa | done | auto-ci-cd-84e209103f24 | S00-001, S00-011, S00-013, S00-014, S00-016 | Устранить прерывание: Fix runner prompt delivery after partial stdin timeout |
| [S00-016](tasks/S00-016.md) | S00 | ci-cd | qa | done | auto-ci-cd-351a0cbbabef | S00-001, S00-011, S00-013, S00-014 | Restore reviewer model availability after provider capacity failure |
| [S00-017](tasks/S00-017.md) | S00 | protocol | security | done | auto-protocol-599343cd9e3f | S00-001, S00-002 | Establish fail-closed SCRAM server-final validation |
-| [S00-018](tasks/S00-018.md) | S00 | protocol | security | todo | | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
+| [S00-018](tasks/S00-018.md) | S00 | protocol | security | review | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
| [S00-010](tasks/S00-010.md) | S00 | qa | orchestrator | todo | | S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 | Gate: Профиль и проверка стека |
| [S01-001](tasks/S01-001.md) | S01 | orchestrator | security | todo | | S00-010 | Описать identity/model contracts |
| [S01-002](tasks/S01-002.md) | S01 | orchestrator | security | todo | | S00-010, S01-001 | Описать async account context |
diff --git a/planning/stages/S00.md b/planning/stages/S00.md
index de4697d..6ae7ed5 100644
--- a/planning/stages/S00.md
+++ b/planning/stages/S00.md
@@ -22,7 +22,7 @@
- [S00-015](../tasks/S00-015.md): Устранить прерывание: Fix runner prompt delivery after partial stdin timeout — ci-cd; done.
- [S00-016](../tasks/S00-016.md): Restore reviewer model availability after provider capacity failure — ci-cd; done.
- [S00-017](../tasks/S00-017.md): Establish fail-closed SCRAM server-final validation — protocol; done.
-- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; todo.
+- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; review.
- [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo.
## Граница этапа
diff --git a/planning/task_matrix.csv b/planning/task_matrix.csv
index a8507bc..417c356 100644
--- a/planning/task_matrix.csv
+++ b/planning/task_matrix.csv
@@ -15,7 +15,7 @@ S00-014,S00,ci-cd,qa,done,auto-ci-cd-14343249da62,"S00-001, S00-011, S00-013",Fi
S00-015,S00,ci-cd,qa,done,auto-ci-cd-84e209103f24,"S00-001, S00-011, S00-013, S00-014, S00-016",Устранить прерывание: Fix runner prompt delivery after partial stdin timeout
S00-016,S00,ci-cd,qa,done,auto-ci-cd-351a0cbbabef,"S00-001, S00-011, S00-013, S00-014",Restore reviewer model availability after provider capacity failure
S00-017,S00,protocol,security,done,auto-protocol-599343cd9e3f,"S00-001, S00-002",Establish fail-closed SCRAM server-final validation
-S00-018,S00,protocol,security,todo,,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
+S00-018,S00,protocol,security,review,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
S00-010,S00,qa,orchestrator,todo,,"S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009",Gate: Профиль и проверка стека
S01-001,S01,orchestrator,security,todo,,S00-010,Описать identity/model contracts
S01-002,S01,orchestrator,security,todo,,"S00-010, S01-001",Описать async account context
diff --git a/state/commit_messages/auto-protocol-89fe6563092c.txt b/state/commit_messages/auto-protocol-89fe6563092c.txt
new file mode 100644
index 0000000..ea3baff
--- /dev/null
+++ b/state/commit_messages/auto-protocol-89fe6563092c.txt
@@ -0,0 +1,4 @@
+Bound standalone XML ingress before Stanza parsing and import
+
+Changes: Add an isolated public transport integration with finite byte, depth, element and attribute budgets; validate complete text XML with pinned saxes before real JXT parsing/import. Add adversarial and boundary fixtures, terminal cleanup tests, dependency notices and a proposed parser ADR.
+Validation: Fresh-cache npm ci and npm test passed all 55 tests with Node 24.21.0/npm 11.19.0. Reproduced three stock parser failures, checked ten exact dependency licenses/integrities and 340 unchanged Stanza files; make check passed 72 tooling tests. Scope and whitespace inspected. Browser runtime, live WSS and interoperability were not tested.
diff --git a/state/evidence/S00-018-auto-protocol-89fe6563092c.md b/state/evidence/S00-018-auto-protocol-89fe6563092c.md
new file mode 100644
index 0000000..187c2dd
--- /dev/null
+++ b/state/evidence/S00-018-auto-protocol-89fe6563092c.md
@@ -0,0 +1,118 @@
+# S00-018 executor evidence
+
+Agent: auto-protocol-89fe6563092c (protocol). Date: 2026-10-15.
+Result: bounded standalone XML ingress experiment and proposed ADR; independent security review pending.
+
+Input: assigned master/startup/role/workflow instructions; resume/show/packet;
+required TESTING, decisions/README, blocker S00-018; exact ACC-004/NET-003/NET-004;
+accepted scope/stack/SASL ADRs and all direct dependency evidence/reviews/handoffs;
+original parser blocker evidence/handoff/probe; shipped Client/Transport/JXT and
+saxes API/source. No missing required input. No new shared contract is needed.
+Output: only experiments/0002-xmpp-library/parser-validation and
+ docs/decisions/0002-xmpp-parser-validation.md, plus workflow metadata.
+
+## Actual changes and demonstrated acceptance
+
+Public Client.transports constructor registration and explicit single transport
+selection run real Client.connect. Per-message type/unit/UTF-8 byte checks precede
+new saxes XML 1.0 namespace-aware document parsing. Callback depth/element/
+attribute/name/value budgets precede candidate JXT.parse/registry.import.
+SAX close detects incompleteness; malformed/multiple-root documents never import
+an earlier complete root. All XML syntax uses existing libraries. Global input
+bytes bound unfinished/oversized tokens before event callbacks. No vendor patch,
+late interception, fixture parser or handwritten XML security parser.
+
+Local inclusive budgets: 4096 UTF-8 bytes, depth 8, elements 32, attributes 16
+including namespace declarations, aggregate attribute name+decoded value units
+1024, names 128, decoded attribute values 256. Combined boundary fixture meets
+all aggregate ceilings simultaneously and yields its exact real imported body;
+separate Unicode/name/value equality cases pass. Limits remain experiment policy,
+not product/shared contract decisions. DTD/comment/PI/declaration reject;
+namespace, entities, malformed chars and duplicate attributes checked by saxes.
+
+55 tests use actual Client/JXT/registry/queue. FixtureSocket captures only socket
+effects; registry tracing calls the real import. Original 1 MiB body, depth 256,
+and 18-message partial-document categories reject terminally. Bad input never
+delivers stanza or stream:start, invalid syntax/budgets never reach registry
+import, and old captured callbacks cannot revive the attempt. Callback detachment,
+stream/config cleanup, fresh Client.connect, successive independent documents,
+stream open/close/restart, export/send and subprotocol/socket failure are tested.
+No peer payload/error text is logged or raw-evented.
+
+## Actual commands and results
+
+All Node/npm commands used
+PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH,
+the previously reviewed isolated Node 24.21.0/npm 11.19.0, from project root.
+No system install or certificate override. Exact engine pins and strict SSL
+remain in manifest/.npmrc. No new runtime archive verification claimed.
+
+| Command | Exit | Observed result |
+| --- | --- | --- |
+| python3 scripts/tasks.py resume --agent auto-protocol-89fe6563092c | 0 | Assigned S00-018 in_progress, no repeat claim |
+| python3 scripts/tasks.py show S00-018; packet S00-018 --agent auto-protocol-89fe6563092c (separate invocations) | 0 each | Scope/prerequisites/requirements confirmed |
+| node experiments/0002-xmpp-library/parser-probe/reproduce.mjs | 0 | Stock accepted 18 writes/65536 body bytes, 1048576-byte body, 256 nested children; defect reproduction, not safe result |
+| npm view saxes@6.0.0 version license engines dependencies --json --cache /private/tmp/luma-parser-cache --fetch-retries=0 --fetch-timeout=15000 --strict-ssl=true | 0 | 6.0.0, ISC, Node >=v12.22.7, xmlchars ^2.2.0 |
+| npm install --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-cache --fetch-retries=0 --fetch-timeout=15000 | 0 | Ten packages, independent lock generated |
+| npm test --prefix experiments/0002-xmpp-library/parser-validation | 0 | 55 passed, zero failed/skipped/cancelled, 378.193208 ms |
+| npm ci --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-clean-89fe6563092c --fetch-retries=0 --fetch-timeout=15000 | 0 | Actual clean install with fresh cache, ten packages, scripts disabled |
+| npm test --prefix experiments/0002-xmpp-library/parser-validation after ci | 0 | 55 passed, zero failed/skipped/cancelled, 341.101792 ms; runtime/test source unchanged afterward |
+| curl --fail --silent --show-error https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE -o /private/tmp/luma-parser-saxes-LICENSE | 0 | Upstream full ISC lineage/historical MIT notice retrieved through verified HTTPS; preserved in experiment |
+| Corrected inline Python license collector | 0 | Ten exact installed versions/licenses/HTTPS/SHA512 entries; preserved shipped notices plus upstream saxes LICENSE |
+| python3 experiments/0002-xmpp-library/parser-validation/check-dependencies.py | 0 | Ten exact graph/license checks; all 340 Stanza files byte-match original candidate |
+| make check | 0 | 283 tasks, 22 stages, 221 requirements, 211 Web requirements, revision 44; 72 tooling tests in 18.439s |
+| git diff --check; git status --short --untracked-files=all (separate checks) | 0 each | Scoped source only plus coordinator state claim before evidence |
+| Inline Python authored-file whitespace/final-newline checks | 0 | All 12 authored experiment/ADR files checked, including untracked files |
+
+Exploratory failures are not passed checks: guessed transports/index.d.ts absent
+(actual Transport API is index.d.ts); first license collector failed because
+saxes tarball omits LICENSE (Python AssertionError, surrounding shell ended 0
+because following npm ci succeeded). Guessed upstream root saxes.ts URL returned
+404; subsequent head/rg saw no file (combined command exit 2). Actual upstream
+LICENSE fetch succeeded; no required artifact is missing and no source patch
+was attempted. These failures do not affect the reproduced acceptance checks.
+
+## Primary sources and dependencies
+
+Opened RFC 7395 §3.2/3.3.3 through web tools; versioned saxes README/package source,
+upstream Stanza Client source; inspected installed version-pinned declarations
+and implementation. Direct links and constraints are in ADR. Sources inspected
+2026-10-15. The supported extension claim rests on public shipped constructor
+registry/Transport interface and actual Client selection, not private fields.
+
+New direct pins saxes 6.0.0 ISC / xmlchars 2.2.0 MIT, existing Stanza 12.22.1 MIT.
+Other exact installed packages: @types/node 22.20.5, @types/punycode 2.1.4,
+@types/ws 8.18.2, punycode 2.3.1, sdp 3.2.2, undici-types 6.21.0, ws 8.22.0,
+all MIT. Inventory/notices and lock are preserved. Saxes release is older;
+continued support/advisory/provenance clearance is not assumed. Node-focused
+CommonJS packages require browser bundling. Default adapter uses native browser
+WebSocket, URL, TextEncoder and private fields; fixture injection is explicit.
+
+SHA256:
+- package-lock.json: 35b177d57aaf81778eaf3d3321b3f77eb1e755a46abee784ef62b8959aedd63c
+- bounded-ingress.mjs: e6399ff581f59d1f09b471234d6e4b8f2ed6b61804aa4eb0eef7dd3f8e3f2dbc
+- licenses/saxes-6.0.0-LICENSE: 0fac2374380621b22e6b50451057721a9c52935b02d16d106a9f04897f061d0e
+
+## Limits and independent review input
+
+No browser build/runtime, actual WSS/TLS/server handshake, auth changes,
+independent client interoperability, load/exhaustion benchmark or security audit
+was performed. No TLS/CORS bypass, real credentials/conversations, custom crypto
+or invented binding. Original candidate, reviewed SASL plugin, app modules,
+shared contracts, immutable specs and plan definitions unchanged. Git mutations
+and additional task/agent launch were not performed.
+
+Browser WebSocket allocates a complete inbound message before JavaScript checks;
+this experiment bounds parser work afterward, not network-stack memory. SAX can
+store one token/attribute before a callback, bounded by the byte precheck. Total
+traffic, Client queues, downstream extension work and CPU rate are not bounded.
+Minimal transport lacks full stream-error/close negotiation/redirect/keepalive/
+language inheritance/stream sequencing. Complete SAX/JXT semantic equivalence
+is not certified. See README/ADR; these limits prohibit production-readiness
+claims but do not invalidate the assigned local parser/framing experiment.
+
+Separate security reviewer must reproduce README clean install/tests/license
+checks, inspect actual scope/order/API/cleanup and compare this evidence and
+executor commit message with actual patch. ADR remains proposed and task is
+submitted for review, not done. Coordinator commits and launches fresh review;
+this executor stops after submit and does not resume S00-003.
diff --git a/state/handoffs/S00-018.md b/state/handoffs/S00-018.md
new file mode 100644
index 0000000..9660efa
--- /dev/null
+++ b/state/handoffs/S00-018.md
@@ -0,0 +1,39 @@
+# S00-018 handoff for independent security review
+
+Executor: auto-protocol-89fe6563092c (protocol), 2026-10-15.
+Outcome: isolated bounded XML ingress experiment and proposed parser ADR.
+No blocker; implementation awaits independent security review.
+
+Actual result: new public Client.transports constructor selected by actual
+Client.connect, finite byte precheck, fresh saxes strict namespace-aware XML 1.0
+validation/close per message, then actual JXT.parse/registry.import. Explicit
+local byte/depth/element/attribute/name/value limits apply before tree/import.
+Malformed/incomplete/multiple-root/binary/budget violations close attempt,
+detach callbacks, clear stream/config; stale callbacks cannot revive it. A fresh
+connection works. No persistent parser, vendor patch or custom XML syntax parser.
+
+Pinned Node 24.21.0/npm 11.19.0 fresh-cache npm ci and 55 integrated tests passed;
+stock probe reproduced all three failures; license checks for all ten packages
+and byte comparison of all 340 Stanza files passed. make check: 72 tests passed.
+Commands/digests/failures/limits: state/evidence/S00-018-auto-protocol-89fe6563092c.md.
+
+Next belongs only to security reviewer: inspect proposed
+ docs/decisions/0002-xmpp-parser-validation.md, experiment source/tests/notices,
+executor diff/message and README. Run README commands with the reviewed isolated
+runtime at /private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin and a
+fresh reviewer cache. Verify budget/framing checks precede JXT allocations/import,
+actual Client registry selection, boundary success, zero delivery for bad input,
+terminal callback replay/cleanup and fresh attempts. check-dependencies.py also
+needs the original parent candidate installed for vendor comparison. Saxes
+published package omits LICENSE; preserved v6.0.0 upstream LICENSE and source are
+recorded. Keep ADR proposed until independent acceptance.
+
+Limits: fixture socket only, no browser build/runtime or real WSS/server/client
+interop; byte checks begin after browser allocates the message. No aggregate
+traffic/Client queue/rate bounds or production policy. Minimal transport lacks
+full stream-error/close negotiation/redirect/keepalive/language inheritance/
+sequencing, and exhaustive SAX/JXT equivalence is not certified. Auth/crypto and
+accepted SASL experiment unchanged. No specs/shared interfaces/app modules or
+original candidate files changed. S00-003 remains separate; only reviewed done
+and coordinator commit allow a fresh original executor to continue it. This
+executor exits after submit; no commit/next agent/original work is performed.
diff --git a/state/tasks.json b/state/tasks.json
index afb3701..227db9c 100644
--- a/state/tasks.json
+++ b/state/tasks.json
@@ -1,6 +1,6 @@
{
"schema_version": 1,
- "revision": 43,
+ "revision": 45,
"tasks": {
"S00-001": {
"status": "done",
@@ -1236,10 +1236,14 @@
"completed_at": "2026-10-14T19:20:57+00:00"
},
"S00-018": {
- "status": "todo",
- "owner": null,
+ "status": "review",
+ "owner": "auto-protocol-89fe6563092c",
"external_wait": false,
- "reason": null
+ "reason": null,
+ "started_at": "2026-10-14T19:24:01+00:00",
+ "updated_at": "2026-10-14T19:31:32+00:00",
+ "evidence": "state/evidence/S00-018-auto-protocol-89fe6563092c.md",
+ "handoff": "state/handoffs/S00-018.md"
}
},
"events": [
@@ -1798,6 +1802,29 @@
"from": "in_progress",
"to": "blocked",
"reason": "S00-003 cannot attest parser limits: Stanza 12.22.1 exposes no resource bounds and its persistent WebSocket parser accepts large/deep XML and reassembles incomplete documents across separate input messages."
+ },
+ {
+ "revision": 44,
+ "at": "2026-10-14T19:24:01+00:00",
+ "action": "claim",
+ "task": "S00-018",
+ "agent": "auto-protocol-89fe6563092c",
+ "role": "protocol",
+ "from": "todo",
+ "to": "in_progress",
+ "reason": null
+ },
+ {
+ "revision": 45,
+ "at": "2026-10-14T19:31:32+00:00",
+ "action": "submit",
+ "task": "S00-018",
+ "agent": "auto-protocol-89fe6563092c",
+ "role": null,
+ "from": "in_progress",
+ "to": "review",
+ "reason": null,
+ "evidence": "state/evidence/S00-018-auto-protocol-89fe6563092c.md"
}
],
"retired_agents": [