fix(plugins): complete BQAA privacy and shutdown hardening#11
fix(plugins): complete BQAA privacy and shutdown hardening#11caohy1988 wants to merge 8 commits into
Conversation
Full review of
|
- Use one loop-state lookup\n- Preserve normalized key collisions
|
Addressed both informational findings in
Restored a single
Normalized dictionaries now preserve the first writer under the plain key and reallocate Verification after the combined fix: 407 passed / 6 skipped with |
caohy1988
left a comment
There was a problem hiding this comment.
Code Review — BQAA hardening port
Reviewed with 4 parallel specialist passes (security, concurrency, testing/maintainability, adversarial red-team) against fork/main...pr-11-review; top findings manually verified against the code.
Scope Check: CLEAN — the diff delivers exactly what the description states (2 files, +3897/−552). No scope creep.
High-confidence findings (multi-source confirmed)
1. [P1] bigquery_agent_analytics_plugin.py:2297 — full row payloads dumped to logs on non-retryable write errors.
logger.error("Row content causing error: %s", rows)Prompts, user messages, tool args — unredacted — land in application logs precisely when content is weird enough to fail server-side validation. Directly contradicts the "constant non-payload logs on failure" claim. Fix: delete it or log len(rows) only.
2. [P1] bigquery_agent_analytics_plugin.py:4001-4013 — _schema_fields_match compares field NAMES only.
existing_by_name = {f.name: f for f in existing}
...
if existing_field is None:
new_fields.append(desired_field)
elif (desired_field.field_type == "RECORD" and existing_field.field_type == "RECORD" ...):A pre-existing table with content as STRING instead of JSON (the common "table from an older deployment" case) passes readiness, _started=True, and every AppendRows then dies as non_retryable for the life of the process — exactly the silent-write-death scenario the fail-closed readiness claim targets. Fix: compare field_type and mode; treat mismatch as unready.
P1 — remaining (each verified or high-confidence)
- [P1] Content/prompt path gets no credential redaction. The mandatory final pass (
_recursive_smart_truncate, :5487) runs onattributesonly;content/content_partsget length truncation only (:2679-2683, :2787-2788). A user message containing{"access_token": "..."}or service-account JSON is stored verbatim — and_SENSITIVE_KEYS(:415-422) omitsprivate_key,token,secret,authorization. GCS-offloaded text also uploads raw before any sanitizer sees it. Biggest privacy gap in the PR. - [P1]
before_modelshort-circuit leaks thellm_requestspan. Push at :6078, pop only inafter_model_callback(:6166). When a guardrail/cache callback returns anLlmResponse,base_llm_flow._call_llm_with_tracingshort-circuits (yield response; return) —after_model_callbacknever runs — and every subsequent row in the invocation gets a wrongspan_id/parent_span_id.on_agent_error_callbackis kind-guarded (:6363); this path isn't. - [P1]
after_run_callback→flush()→ unboundedqueue.join()(:2018). Each failing batch costs up to 30s × (retries+1) + backoff, serialized — a BQ outage parks the agent's own run completion for minutes (PluginManageronly timeboxesclose()). Also: retry after a client-side timeout on the_defaultstream re-sends a batch that may have committed server-side → silent duplicate rows. - [P1]
:2400-2404— external cancellation landing on the worker-ackawaitis swallowed as the worker's own ack; shutdown then reports success. Fix: re-raise ifcurrent_task().cancelling() > 0. - [P1]
:3652-3654— after an incomplete shutdown, the retained state (dead worker,_shutdown=True) is handed back by_get_loop_state;_startedflips True and rows are appended to a queue nothing drains — silently lost, uncounted. Fix: treat a state with a dead processor as absent and rebuild. - [P1]
:4445—await state.write_client.transport.close()has no timeout. A hung gRPC close blocks the shutdown owner forever; coalesced waiters at :4278 (shield(wrap_future(waiter)), no timeout) hang with it. Same pattern at :3720, :4918. Fix:asyncio.wait_for(..., timeout=t). - [P1]
:4939-4945—_teardown_aborted_setupawaitsexecutor.shutdown(wait=True)unbounded while the_setup_futurerendezvous is still held; a wedged constructor job wedges all future setup waiters forever. - [P1] (py3.10-specific)
:2201-2205— worker suppresses cancellation in its error-recovery sleep and returns normally; pre-3.11wait_forthen returns instead of raising, so the shutdown drain is skipped and queued rows vanish uncounted.requires-python >= 3.10makes this live.
P2 — notable
- Fail-closed JSON heuristic guts legitimate telemetry (:769, :814-822): any string starting with
[/{that doesn't parse becomes[UNPARSEABLE_JSON_BLOB].[INFO] server started-style tool output is the common case in production; at volume this silently destroys the args/result analytics the table exists for. error_message=str(error)written unsanitized/unbounded (:6215, :6332, :6380, :6430) — API exceptions routinely embed request bodies/tokens; bypasses every redaction pass. Same forerror_traceback(:6366-6373, :6415-6422).- Prose-embedded credential JSON bypasses blob redaction (PoC-verified): strings not starting with
{/[return uninspected (:769-770);note: {"access_token":"SECRET"}persists verbatim. - Coalesced shutdown waiter swallows any non-
_ShutdownIncompleteErrorand returns success (:4286-4288). - Shutdown "budget" is ~4× claimed — each of 4 sequential phases gets the full
t(:4304, :4380, :4467, :4491). - Duplicated claim-fold accounting (:3494-3517 vs :4334-4354, ~20 lines verbatim) and duplicated key-normalization with two inconsistent sentinel dialects (:580 vs :1135-1141) — extract shared helpers before they drift.
BatchProcessor.close()keeps its own inline drain (:2457-2471) that pops the sentinel without decrementing_sentinel_count, skewing the O(1) accounting used everywhere else. Call_drain_queue_and_count("shutdown_timeout")there too.- Vacuous test:
test_get_loop_state_uses_single_lookup(test:12883) installs a__contains__hook the source never calls (it uses.get()) — passes with the source change reverted. Delete or fix. - Flaky timing assert:
assert elapsed < 0.15(test:12399) will fail on loaded CI runners; the thread-identity assert is the real invariant. - Coverage gaps: executor-retention branch on cancelled setup (:4792) and the done-but-not-cancelled worker arm (:2388-2390) have no tests.
__getstate__doesn't scrub_credentials(:4557-4578) — pickling the plugin serializes live OAuth refresh tokens/private keys.- DDL identifier interpolation (:4155, :3310-3318) —
view_prefix/dataset_id/table_idgo intoCREATE OR REPLACE VIEWraw; trusted-config-only, but a backtick breaks out. Validate^[A-Za-z0-9_]+$at construction. - Partial-commit misaccounting (:2293-2298): AppendRows with row-level errors can commit valid rows, yet the whole batch is counted as
non_retryabledropped — the loss signal over-reports while the timeout-retry path under-reports duplicates. atexit.registerper loop-state, never unregistered (:3725) — unbounded accumulation under loop churn.- Parser-None window drops escape accounting (:5406-5408) — no
_count_local_drop, unlike every sibling early-return. on_run_error_callbacknever flushes (:6388-6441) — error-terminated invocations lose their final queued rows in short-lived processes.- The 6 test skips are pre-existing optional-extra guards, but the net effect is zero coverage of the
TRANSFER_A2A/MCPclassification branches in the reported run — consider a CI job with the extras installed.
Verified as holding (specialists probed and could not break): double/unicode-encoded credential layers, temp: keys, StrEnum/namedtuple redaction, BOM/whitespace linearity, fail-closed formatter sentinels, shutdown coalescing/generation guards, O(1) queue accounting, loop-affinity of remote drains, orphan-client close on cancelled setup.
Verdict
Request changes. The architecture is sound and most hardening claims hold under adversarial probing, but four findings undercut the PR's headline guarantees: the row-payload log (#1), name-only schema readiness (#2), the unredacted content path, and the span leak. Those plus the cancellation-swallow and dead-processor-handback (both small and mechanical) should land in this PR; the rest can be follow-ups.
|
Addressed the six requested P1s in
Verification at
I left the other timeout-budget, Python 3.10 recovery, error-payload sanitization, retry/idempotency, and maintainability findings for follow-ups, matching the review verdict. One review item was not changed: |
caohy1988
left a comment
There was a problem hiding this comment.
Follow-up Review — commit 40f8a36
Re-reviewed with two verification passes (privacy+schema, concurrency+span), including empirical checks on CPython 3.10.16 / 3.11.13 / 3.12.9 / 3.13.5 and mutation-testing of the new tests.
Verdict: 4 of 6 fixes verified solid. Fix 5 is broken on Python 3.10. Fix 3 left a previously-flagged column raw.
Fix-by-fix
| # | Fix | Status |
|---|---|---|
| 1 | Row payload log removed | ✅ Verified — count-only log at :2298 |
| 2 | Schema type/mode compatibility | ✅ Verified — raises before any update, fails setup closed (_started never set, bounded backoff retry), mode can't be None, nested path tracking works. New tests are mutation-proof. |
| 3 | Content credential redaction | |
| 4 | Span leak on short-circuit | ✅ Verified — all six flows walked (normal, streaming, later-plugin short-circuit, own-plugin, model error, sequential calls); the kind-guarded pop can only take the correct span. Tests fail if the pop is removed. (Minor correction to the PR response: the defensive pop is in before_model_callback, not after_model_callback — placement is correct.) |
| 5 | External cancellation preserved | ❌ Broken on 3.10 — see below. Correct on 3.11+ (verified: no false-positive re-raise on the normal timeout path). |
| 6 | Dead processor replacement | ✅ Verified — pop is single-winner under guards, no double-counting, the "still shutting down" RuntimeError path drops and counts the event, detached close fully bounded. |
❌ [P1] Fix 5 crashes on Python 3.10
bigquery_agent_analytics_plugin.py:2418:
current_task = asyncio.current_task()
if current_task is not None and current_task.cancelling():Task.cancelling() was added in 3.11; the repo declares requires-python = ">=3.10" and CI tests 3.10. Verified on 3.10.16: hasattr(asyncio.Task, "cancelling") is False, no compat shim.
Normal timeout path on 3.10: wait_for cancels the worker → TimeoutError → await worker raises CancelledError → handler calls .cancelling() → AttributeError, escaping shutdown()/close(), skipping _drain_queue_and_count, and replacing the very CancelledError the fix was meant to preserve. The pre-existing test_shutdown_timeout_counts_lost_rows should also fail on 3.10 — expect the 3.10 matrix job to go red.
One-line fix:
if current_task is not None and getattr(current_task, "cancelling", lambda: 0)():⚠️ [P1] error_message column still stored raw
Flagged in the first review, untouched by this diff. :5654 writes "error_message": event_data.error_message straight into the row from str(error) (:6369, :6486, :6534, :6584) — bypassing both the parser and the attributes sanitize pass. google.auth exceptions routinely quote tokens/headers. error_traceback gets partial coverage via the blob sanitizer, but prose passes through. Fix: run both through _recursive_smart_truncate and cap to max_content_length before row assembly.
⚠️ [P2] Fix 3 quietly extends destroy-on-bracket semantics to ALL content
_sanitize_raw_text means any user message/prompt whose first non-whitespace char is [, {, or " and isn't valid JSON becomes [UNPARSEABLE_JSON_BLOB] — in the row, the summary, AND the GCS object. Verified: [INFO] please summarize this, [link](https://x.com), {not json} are all wholesale destroyed. This was a P2 in the first review when it only affected attributes; it now covers 100% of content. Fail-closed is defensible, but it should be a conscious, documented trade-off — right now no test pins it as intended for content. Consider a truncated-prefix sentinel (first 200 chars + [UNPARSEABLE_JSON_BLOB]) so analytics keep signal.
New P3s (follow-up material)
- :3735 — the "still shutting down" RuntimeError routes through generic setup-failure handling, arming exponential backoff and misclassifying later drops as
setup_unavailableinstead ofshutdown_race. Self-limiting. - :3771 — if fresh-state construction raises after the detached-close task is created, the task is never retrieved → "Task was destroyed but it is pending" noise at loop teardown.
- :3840 (pre-existing) — the invalidated-publication branch still does an unbounded
await transport.close(); the new bounded helper is right there to copy. - :2636 —
file_data.file_uristored raw; signed URLs carryX-Goog-Signatureintocontent_parts(structurally outside the JSON sanitizer's reach). - :2726-2731 —
function_response/executable_code/code_execution_resultparts fall through_parse_content_objectunhandled; all-Nonerows incontent_parts(completeness gap, not privacy).
Bottom line
Fixes 1, 2, 4, 6 are merge-quality — well-implemented and mutation-tested. Blocking: the 3.10 cancelling() crash (one line) and the raw error_message column (small). The bracket-prose destruction needs an explicit decision, not necessarily code. The rest is follow-up.
|
Addressed the follow-up review at review 4731753868 and pushed the fixes as
Two issues found during the final independent pass were also closed before push: Authorization header redaction no longer produces an extra Verification at
GitHub CI is now fully green from |
caohy1988
left a comment
There was a problem hiding this comment.
Round-3 Review — commit 83ee867
Verified with two passes (adversarial privacy/sanitizer, concurrency/lifecycle), including empirical repros on CPython 3.10.16 / 3.12.9 / 3.13.7 and live re-runs of the new tests against the v3 source.
Headline: all claimed fixes work. No P1s. The concurrency/lifecycle half is clean. The new _sanitize_sensitive_text has 5 P2-grade evasion/false-positive classes worth one more iteration.
Claimed fixes — verification results
| Fix | Status |
|---|---|
| Python 3.10 cancellation | ✅ Verified — shield(gather(task, return_exceptions=True)) is correct and version-portable. Empirically repro'd on 3.10/3.12/3.13: normal timeout drains and counts; host cancel propagates; no unretrieved-exception warnings. New tests run live against v3 and genuinely guard this. |
Raw error_message/tracebacks |
✅ Fixed — all four producers route through the new _log_event boundary (:5833), unconditional on event type, fail-closed on sanitizer exception, single row writer. But see FP finding #2 below. |
| Bracket-led prose | ✅ Fixed — [INFO], markdown links, {not json} preserved byte-identical, inline and in GCS. Fail-closed side holds (escaped keys, temp:, valid credential JSON still destroyed). Genuine pinning test. |
| Lifecycle accounting | ✅ Verified — _LoopStateAdmissionAbortedError provably never touches _startup_error/backoff; owner+waiter both get "aborted"; retry after drain succeeds. |
| Bounded transport cleanup | ✅ Verified — detached close now in a finally with structured ownership; every fresh-construction failure/cancellation interleaving closes the client. The pre-existing unbounded close is gone. |
| Signed URI redaction | ✅ Mostly — userinfo/scheme-less/duplicate/percent-encoded keys/fragment/gs:// all held under attack. Residual gaps below. |
| Structured-part handling | ✅ Fixed — function_response/executable_code/code_execution_result captured with structural + per-string sanitization; depth/budget can't be bypassed. |
New P2s — all inside the new text sanitizer (empirically confirmed)
access_token=[REDACTED]SECRETpasses verbatim (:541-546, conf 9). The[REDACTED]idempotency carve-out matches as a prefix of the value, so anything upstream that pre-redacts a prefix becomes an exfil channel. Fix: only treat[REDACTED]as safe when it's the entire value, e.g.\[REDACTED\](?![^\s,;&}\]]).- Backslash+marker nukes entire innocuous error messages (:561-565, :494, conf 9). Any message with a literal
\and any marker substring →[REDACTED_SENSITIVE_TEXT]. Empirically destroyed:FileNotFoundError: 'C:\Users\secret\config.json',json.decoder.JSONDecodeError: Invalid \escape, and worst — CPython's standardSyntaxError: ... can't decode \x5c in position 0fails closed with no credential marker at all (\x5calone is treated as "introduced escape"). Windows-path and JSON-decode errors are the most commonerror_messagecontent in production — this blanks exactly the rows the fix was meant to protect. No test pins this. Fix: only fail closed when a decoded marker is actually found, not on the mere presence of\x5c. Authorization: Digest SECRETredacts only the scheme word (:465-468, conf 8) — the scheme word is consumed as the credential; the payload survives. AffectsDigest,Negotiate,AWS4-HMAC-SHA256 Credential=...in 401/403 error text.- URI path component never inspected (:2763-2807, conf 8) —
https://h/download/TOKEN/file.zipsurvives verbatim; only query/fragment are sanitized. Token-in-path links are a real credential class. - Urlencoded
access_token%3DSECRETevades (:459-463, conf 9) — signed-URL material often appears double-encoded in HTTP error text.
New P3s
- Prose-restore gate is weaker than the sanitizer:
[log] BEARER\tX,bearer\nX,sig=X,Basic dXNlcg==restored verbatim (:2748). sig=,X-Amz-Signature,X-Amz-Credential,GoogleAccessIdnot in the key list;bearer\nevades[ \t]+(:469).function_responsedict keys never text-sanitized —{url_with_sig: metadata}maps leak key-side (:2827).- Prose corruption: "the bearer of bad news" → "the bearer [REDACTED] bad news" (:469).
- Host-cancel during the shield lands inside the TimeoutError handler, so the sibling CancelledError handler is bypassed — rows are counted later via the retry/fold paths instead of immediately (:2567-2577, conf 9; loss still eventually observable, just delayed).
- Narrow leak: host-cancel during the invalidated-publication
batch_processor.shutdown()skips the fresh transport close (:4184-4193, conf 7). model_dump()runs before any size budget — multi-MB tool responses serialize unbounded on the event loop (:2789, conf 6).test_normal_timeout_retrieves_worker_cancel_and_drains_queueis only meaningful on 3.10 — nearly vacuous on ≥3.11 (the old code passed it there too). Its guard value depends on CI keeping the 3.10 job.- Untested attack surface: the
[REDACTED]-prefix carve-out, Digest schemes,%3D, path credentials, the backslash-FP class.
Held under attack (verified, don't re-litigate): ReDoS (all three regexes linear, timed to 350k chars); prose-restore fall-through keeps the sentinel on valid JSON; URI userinfo/scheme-less/encoded-key/fragment handling; part-model depth/budget; error-producer coverage; all concurrency fixes listed above.
Bottom line
This is close. The structural work — cancellation, lifecycle, schema, spans, redaction plumbing — is done and well-tested. What remains is one more iteration on _sanitize_sensitive_text's pattern table: fix the [REDACTED]-prefix hole and the backslash false-positive (those two are worth blocking on; the first is an exfil channel, the second blanks common production error rows), and consider the path-component and %3D gaps. Everything else is follow-up material.
|
Addressed round-3 review
The remaining review notes about immediate host-cancellation accounting, pre-budget Verification at
GitHub Actions is green across pre-commit, mypy on Python 3.10–3.13, unit tests on Python 3.10–3.14, and A2A v0.3 tests on Python 3.10–3.14. The PR is mergeable at this head. |
caohy1988
left a comment
There was a problem hiding this comment.
Round-4 Review — commit 171db08 — LGTM ✅
Verified with two passes (adversarial sanitizer battery + test/regression verification), including a full suite run in an isolated worktree: 439/439 pass in 29.6s, matching the claimed local results. Every round-3 PoC was re-run against the real module.
Round-3 P2s — all closed or deliberately scoped
| # | Finding | Status |
|---|---|---|
| 1 | [REDACTED]SECRET exfil |
✅ Closed — exact-match exemption only; all suffix variants redact |
| 2 | Backslash false-positives | ✅ Closed — Windows paths / Invalid \escape / \x5c SyntaxError byte-identical; access\u005ftoken, %3D, %253D, nested %255F still fail closed. Canonicalizer linear (1M chars / 193ms, no ReDoS) |
| 3 | Digest/multi-scheme auth |
✅ Closed — full redaction to end-of-line incl. AWS4-HMAC-SHA256 Credential=... |
| 4 | URI path credentials | ✅ Closed for labeled segments (incl. encoded/hyphenated labels); unlabeled high-entropy path tokens accepted as out-of-pattern scope |
| 5 | bearer\n / Basic auth |
✅ Closed for padded/colon Basic and \n/\t bearers; prose FPs correctly preserved |
All 6 new tests verified genuine — each was traced against the previous commit and fails on the old code. The invalidated-publication finally change is correct in all three control flows (success / Exception / CancelledError) with no silent-teardown path. Scope: clean, every hunk maps to a round-3 finding.
Two documented trade-offs to note (non-blocking, follow-up material)
- Corrupt/truncated Basic tokens pass verbatim (:548-551) —
Basic dXNlcjpwYXNzXJ9fails base64 validation and is emitted unchanged, but the credential is recoverable (user:pass). Suggested follow-up: redact on validation failure when the blob is credential-length. - Path-segment redaction over-fires on benign endpoints (:2848-2861) —
/oauth/token/refresh→/oauth/%5BREDACTED%5D/%5BREDACTED%5D. Availability trade-off, fail-closed direction; consider keeping the label segment and redacting only the successor in a follow-up.
Remaining P3s for the backlog: base64url charset gap, credential-as-query-key, [REDACTED] SECRET2 suffix exemption, "bearer bonds" prose FP, obs-fold continuation lines, redaction-not-flagged in truncation telemetry, exact-match key policy on part models, stale comment at :444-446.
Verdict
LGTM — merge. Three rounds of structural findings (privacy boundaries, schema compatibility, span hygiene, cancellation correctness across 3.10–3.14, lifecycle accounting, resource ownership) are all verified fixed with genuine regression tests. The sanitizer converged from an exfil-channel + mass-FP state to a tight pattern table with only edge-semantics trade-offs left, both documented above. Nothing outstanding blocks the PR's fail-closed, honest-shutdown, bounded-sanitization guarantees.
|
Upstream synchronization complete.
The first post-merge run compared against the fork's old |
Summary
Current
maincontains the baseline BigQuery Agent Analytics hardening, but it does not include the later privacy, shutdown, and resource-lifecycle fixes validated during google#6360. This PR ports that complete reviewed implementation and regression corpus onto the fork's latest synchronizedmain.The result is a BQAA pipeline that fails closed around formatter/parser output, prevents encoded or adversarial values from republishing sensitive content, performs bounded linear sanitization, and reports shutdown success only after all owned work and clients are actually drained or closed.
Review follow-ups
Commit
40f8a362addressed the six P1s requested in review 4731058331: count-only write-error logs, recursive schema type/mode validation, pre-storage content redaction, model-span cleanup on short-circuit, host-cancellation propagation, and atomic dead-writer replacement.Commit
83ee8673addresses review 4731753868 and its P3 follow-ups:gather(..., return_exceptions=True)acknowledgement owner. This avoids the unavailableTask.cancelling()API while still distinguishing the worker's expected cancellation from host cancellation; agetattrfallback alone would have swallowed host cancellation on 3.10.error_messageand AGENT/INVOCATION tracebacks now pass through one bounded diagnostic privacy boundary. Structured/encoded credentials, sensitive key fragments,temp:values, Authorization/Basic/Bearer headers, signed-query material, and Unicode/hex-escaped sensitive keys are redacted or fail closed; safe prose remains exact and content loss setsis_truncated._shutdownnow returns a distinct lifecycle-aborted outcome to both the setup owner and coalesced waiters, without arming setup backoff or tearing down the retained writer. The row owner records exactly oneshutdown_raceloss.function_response,executable_code, andcode_execution_resultparts now emit bounded, JSON-native, sanitized summaries andcontent_partsinstead of empty placeholders.[INFO], Markdown links,{not json}, and safe Windows-path diagnostics is preserved intact inline and in GCS, while malformed or encoded credential-shaped input still fails closed. The stricter generic attributes sanitizer is unchanged.Commit
171db081addresses review 4736064808 as one sanitizer-class fix:[REDACTED]is idempotent only as a complete value; suffix mutations includingSECRET,]SECRET, and/SECRETare wholly redacted.sig, AWS, Google, and X-Goog forms) now share the mapping, diagnostic, bracket-content, signed-URI, and structured-part privacy policy; hyphen/underscore forms are equivalent.finallyeven when cancellation interrupts their processor shutdown.The earlier claim that
test_get_loop_state_uses_single_lookupis vacuous was also rechecked and remains non-reproducible: reverting to membership-plus-index triggers its mutating__contains__hook and raisesKeyError.Upstream synchronization
The fork's
mainand this PR are synchronized with google/adk-pythonmainatf1a0a148. The upstream BQAA changes merged without conflicts, including6e43800f, which makesflush()wait for dequeued in-flight writes viaQueue.join(), plus deterministic write-synchronization tests and use of the platform thread helper in multi-threaded tests. The combined plugin suite passes with both the upstream synchronization changes and this PR's shutdown/privacy hardening.Testing plan
python -W error::RuntimeWarning -m pytest -q tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py434 passed, 6 skipped19 passedTask.cancelling()pyink --checkon the two changed filesisort --check-onlyon the two changed filesruff checkon the two changed filesgit diff --checkAs an independent regression check, the original 404-test corpus produced 57 failures plus one teardown error when run against unmodified latest upstream
main; the expanded 434-test suite passes with this branch. GitHub's Python 3.14 job also exposed and now covers the decoder's runtime-specific acceptance of extreme JSON nesting.Related: google#6356
Related: google#6360