Skip to content

feat: add modular artifact format validation - #2158

Merged
qinxuye merged 8 commits into
xorbitsai:mainfrom
qinxuye:feat/artifact-validation
Sep 6, 2026
Merged

feat: add modular artifact format validation#2158
qinxuye merged 8 commits into
xorbitsai:mainfrom
qinxuye:feat/artifact-validation

Conversation

@qinxuye

@qinxuye qinxuye commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduce modular, skill-independent artifact format validation, separate from tool execution success.

  • Compose ordered checks through one registry, with initial support for XLSX/CSV/TSV, DOCX, PPTX, PDF and common raster images.
  • Validate immutable snapshots in bounded parser subprocesses, cache reports by content and filename, and distinguish valid, invalid and unchecked outcomes. Public validation can occupy at most one of the two parser slots, preserving capacity for authenticated/tool callers.
  • Integrate with generated Python/JavaScript artifacts and workspace writes without changing ordinary registration or removing repair/download handles. Reports absent from other producers are explicitly NOT RUN, not conflated with an inconclusive check.
  • Recheck persisted sandbox outputs on the host instead of trusting guest validation claims.
  • Expose current-file validation through the existing authorized preview routes. The server advertises format support per file; unsupported-format controls are hidden, request failures have a separate retryable UI state, and machine statuses are localized.
  • Use recoverable PDF parsing to check readability rather than strict conformance. Share human-readable byte-size configuration parsing with upload limits, log invalid validation configuration, and forward both settings in Compose.

Scope and behavior changes

This provides bounded, reader-based format checks, not business correctness, visual fidelity or task completion. Unsupported formats, absent optional readers and exhausted budgets remain unchecked. Markdown table diagnostics and Base64-to-attachment conversion are separate PRs.

Artifact discovery now also includes .tsv, .bmp, .tif, and .tiff through the validator registry. These outputs are registered/exposed as generated artifacts where previously they were not auto-discovered. This does not add a new preview engine or promise visual/business correctness.

Earlier review validation

  • Backend artifact/configuration/producer/sandbox/file-route regression suites: 689 passed.
  • Widget frontend regression and coverage suite: 1,144 passed; coverage thresholds passed.
  • Frontend CI manifest: 82 passed.
  • Pre-commit passed, including package-wide mypy, lint, formatting and TypeScript checks.
  • Base Compose plus BoxLite and Docker sandbox overlays validate successfully.
  • New regressions cover recoverable PDF xref offsets, identical bytes under different names/extensions, all five reviewed OOXML package guards, public/private parser capacity, server-advertised unsupported formats, request failures/timeouts, and Chinese UI presentation.

No docs files were added or modified, and no running user services were restarted.

Latest review follow-up (63008c4)

  • Delegate OOXML main-part resolution to real readers after all ZIP/XML safety checks; renamed and genuinely missing main parts are covered for XLSX/DOCX/PPTX.
  • Ambiguous CSV quoting returns unchecked rather than invalid or blindly accepting lenient record recovery.
  • Detect silent-empty standalone FlateDecode recovery as unchecked while preserving valid empty content and successful nonempty recovery. This is not complete PDF filter-chain or visual integrity validation.
  • Respect stricter inherited Linux address-space limits; cover multi-frame GIF/TIFF and BMP/TIF/TIFF readers.
  • 305 focused backend/producer/file-route tests passed on the current head; 75 are format-validation tests. Applicable pre-commit checks passed, including package-wide mypy.
  • Preserve the scope: public-capacity isolation is not per-user/tool scheduling; a killable parser process is not a security sandbox; safe preflight failures are never overridden by later readers.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust format-readability validation framework for generated artifacts (such as Office documents, PDFs, CSVs, and images) to ensure they are readable before delivery. It includes backend checks run in isolated subprocesses, integration with workspace file references, and a frontend ArtifactValidation component. The review feedback identifies three key improvements: removing an invalid reset_dimensions() call on read-only worksheets in openpyxl to prevent AttributeError failures, explicitly setting binary mode for standard input on Windows to avoid binary data corruption, and logging original exception details when redacting subprocess errors to preserve debug traces.

Comment thread src/xagent/core/artifact_validation/office.py
Comment thread src/xagent/core/artifact_validation/worker.py Outdated
Comment thread src/xagent/core/artifact_validation/service.py
@qinxuye
qinxuye requested a review from rogercloud September 6, 2026 11:43

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major

  • src/xagent/core/artifact_validation/formats.py:48PdfReader(..., strict=True) promotes correctable structural quirks (xref issues, bad object offsets, missing /Length) common in real-world non-Adobe PDFs into fatal PdfReadError, mapped to InvalidArtifact; a perfectly usable PDF gets reported to the model as "INVALID. Repair and recheck." Use strict=False (pypdf's own default) or map strict-only failures to unchecked instead of invalid.
  • tests/core/test_artifact_validation.py — cache tests never exercise identical byte content under two different filenames/extensions. The cache key at src/xagent/core/artifact_validation/service.py:128 is (sha256, filename, max_bytes); a regression that drops filename from the key would go undetected. Add a same-bytes-different-filename cache test.
  • src/xagent/core/artifact_validation/office.pycheck_package's five zip-bomb/path-traversal guards (absolute/.. member rejection ~L30-31, duplicate-member rejection ~L30, missing-required-part ~L40, encrypted-entry flag_bits & 1 check ~L46-49, max_entries limit ~L25) have zero test coverage despite being the preflight checks against malicious OOXML archives. Add tests for each guard.

Minor

  • src/xagent/core/artifact_validation/config.py:1818-1822 — bare int() parse with no upper ceiling and no shared parser (unlike XAGENT_MAX_UPLOAD_SIZE's "100M"/"1G" support); a value like "32M" throws ValueError that service.py:82-83 silently swallows into permanent unchecked with zero log line. Reuse a shared size parser and log a warning on invalid config.
  • src/xagent/web/api/files.py — the unauthenticated public_preview_file route with ?validation_only=true shares the process-global BoundedSemaphore(2) (service.py:28) with the authenticated preview path; a handful of concurrent anonymous requests can starve other validations to unchecked for up to the timeout. No rate limiting exists. Add per-user/IP throttling on this parameter.
  • src/xagent/core/tools/artifacts.py:336, workspace_file_tool.py:367,385 pass validate=True but pptx_tool.py (~L2019,2085) and image_tool.py (~L692,832) don't for registry-supported formats — combined with _format_artifact_lines (artifacts.py:203-218) showing identical "UNCHECKED" text for "never checked" vs "checked, inconclusive", a byte-identical .pptx reads differently to the model depending on which tool produced it. Extend validate=True to these producers, or distinguish "not validated" from "validated, unchecked" in the text.
  • frontend/src/components/file/inline-file-preview.tsx:869-878 — wraps every non-audio/video attachment in ArtifactValidation regardless of registered validator support, so unsupported types (.txt/.md/.json/.html) permanently show "File not checked" plus a useless "Recheck" button (confirmed via artifact-validation.test.tsx:79-84 for notes.txt). Gate on a server-advertised supported-extension list.
  • frontend/src/components/file/artifact-validation.tsx:57-58 — HTTP 403/500, network errors, the 20s client abort-timeout, and a genuine "no validator installed" report all collapse into the same "File not checked" UI state with no distinct error state.
  • artifact-validation.tsx:29-32,86 — server-generated check messages (e.g. "PDF header is missing.") render as raw untranslated English literals even in zh locale; no message-code-to-i18n-key mapping exists.
  • src/xagent/core/tools/artifacts.py:37 — unions GENERATED_ARTIFACT_EXTENSIONS with the validator registry's extensions, silently causing .tsv/.bmp/.tif/.tiff to now be treated as inline-previewed generated artifacts wherever they weren't before. Call this out explicitly in the PR description as a behavior change.
  • example.env documents XAGENT_ARTIFACT_VALIDATION_MAX_BYTES/XAGENT_ARTIFACT_VALIDATION_TIMEOUT_SECONDS but neither appears in docker-compose.yml (which does forward the sibling XAGENT_MAX_UPLOAD_SIZE) nor in src/xagent/web/README.md's config list.

Simplification

  • src/xagent/core/artifact_validation/registry.py: ArtifactCheckRegistry (register/supports/extensions/validate, with duplicate-name/format guards) is instantiated exactly once in defaults.py:10-25 as a static list of 5 hardcoded checks — no second instance or runtime/plugin registration point anywhere. Replace with a plain tuple of ArtifactCheck entries plus free functions supports(checks, filename) / validate(checks, content) -> ValidationReport, preserving ordering, short-circuit, exception dispatch, and byte-budget preflight without the class/private-dict indirection.
    net: -25 lines possible

Blocking: yes — recommended event: COMMENT

  • src/xagent/core/artifact_validation/formats.py:48, Major, valid non-strict-conformant PDFs get misreported as INVALID to the model, [new]

@qinxuye

qinxuye commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

@rogercloud Addressed the review on 9ff1357 in 76bd114.

Major findings

  1. PDF strictness — fixed. The new regression builds a PDF with a recoverable startxref offset: strict=True raises PdfReadError, while the ordinary reader opens its page. Validation now uses strict=False; genuinely unreadable PDF data still fails. This was a reproducible behavior bug, not just a hypothetical compatibility concern.
  2. Filename-aware cache — covered. Identical bytes are checked as one.csv, two.csv, and one.pdf, with repeated calls to each name. The CSV results are valid, PDF invalid, and exactly three worker calls occur; dropping either filename discrimination or extension discrimination is caught.
  3. OOXML guards — covered. Tests exercise absolute and parent-traversal names, duplicate members, each required-part omission, the entry-count limit, and encrypted-entry flags in actual ZIP headers. Assertions check the preflight result/message rather than merely accepting a later reader failure.

Other changes and chosen boundaries

  • Configuration: validation/upload limits share a positive byte-size parser (32M, 1.5MB, raw bytes, etc.). Non-finite values are rejected and invalid validation configuration logs a warning. Both validation settings are explicitly forwarded to backend/worker Compose services. The byte budget remains operator-controlled rather than adding an arbitrary second hard ceiling; the default snapshot/expansion/time budgets and Linux worker memory cap remain intact.
  • Public capacity: implemented resource isolation rather than a new per-IP/user rate-limiting subsystem. Public validation can occupy at most one of the two parser slots; excess concurrent public validation returns unchecked without joining that public queue. A concurrency regression holds public work open, verifies another public request is rejected, and proves a private request still completes. Existing guest-token/file authorization remains unchanged. This fixes the reported ability to consume all parser capacity, without claiming general HTTP request throttling.
  • Other producers: chose the review's explicit-state alternative. Missing validation metadata is now NOT RUN, whereas a completed but inconclusive report remains UNCHECKED. Image/PPTX production is not silently presented as having been checked, and this does not add synchronous parser work to their async production paths.
  • Unsupported formats: the response advertises supported from the backend registry. The UI removes the status/recheck controls when false while retaining file access. This is a per-file server capability, avoiding a duplicated client extension list.
  • UI failure state: HTTP failures, malformed responses, network errors and client abort timeouts now use a separate localized retryable request-error state, not a genuine unchecked report. Tests cover failure, retry and timeout.
  • Localization: UI labels are derived from machine status codes in English/Chinese instead of rendering raw English parser diagnostics. Detailed diagnostic messages remain in the API/model report; the UI intentionally presents the localized summary.
  • Discovery changes: the PR description now explicitly calls out newly auto-discovered .tsv, .bmp, .tif, and .tiff artifacts.
  • Documentation: example.env and Compose are updated. No README/docs edits were made, preserving this PR's no-docs scope.

Registry simplification not adopted: the reusable registry is an intentional extension boundary of this PR, with deterministic composition, registration guards, and independently instantiated custom-check coverage. Replacing it with a fixed tuple/free-function interface does not fix a current defect and would change the requested modular API merely to reduce a small amount of code.

Validation: 689 backend tests, 1,144 widget tests with coverage thresholds, 82 CI-manifest tests, all applicable pre-commit hooks, and all three Compose configurations passed locally. CI for the new commit is separate and will rerun. This feedback was submitted as a review summary, not resolvable inline threads; the three earlier inline threads remain resolved.

@XprobeBot XprobeBot added the gpu label Sep 6, 2026
@qinxuye
qinxuye requested a review from rogercloud September 6, 2026 13:20

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major

  • src/xagent/core/artifact_validation/service.py:28,31,88-101_public_slots only fences public=True callers; the authenticated preview_file?validation_only=true polling path (src/xagent/web/api/files.py:1776-1894, driven by ordinary UI inline preview per frontend/src/contexts/file-access-context.tsx:81) and tool-side validation (src/xagent/core/file_ref.py:245-247) both pass public=False and share the same unprotected 2 _slots, so the dominant authenticated traffic can still starve tool-side validation. Only public-vs-private contention is tested (tests/core/test_artifact_validation.py:391-420); add a test for authenticated-vs-authenticated contention or extend the slot split to cover it.
  • src/xagent/core/artifact_validation/office.py:99-102 — reader exceptions (ValueError, KeyError, IndexError, TypeError, OSError, BadZipFile) are unconditionally mapped to invalid, with no distinction between genuine corruption and a reader quirk on a legitimate file — the same problem this PR solved for PDF via strict=False (formats.py:48-50). Apply an analogous fail-open/lenient policy for Office reader errors, and add a test that distinguishes the two cases.
  • src/xagent/core/artifact_validation/office.py:16-20,35-39 and registry.py:78-80 — required-part paths (xl/workbook.xml, word/document.xml, ppt/presentation.xml) are hardcoded instead of resolved via [Content_Types].xml/relationships, and the registry breaks after the first non-valid check so office-reader never runs to correct a false rejection. A genuinely-openable .xlsx with its main part renamed (relationships updated) is rejected invalid even though openpyxl.load_workbook opens it fine. Resolve parts via relationships, or don't short-circuit before the real reader check runs; add a relationship-correct-but-renamed-part test.
  • src/xagent/core/artifact_validation/formats.py:31check_csv uses csv.reader(..., strict=True), rejecting input like a,b\n1,"he said "hi" there"\n that both pandas.read_csv and the non-strict default csv.reader parse fine, contradicting this PR's own readability-over-conformance policy applied to PDF in the same file. Switch to strict=False (or catch and fall back), and add a false-positive test (only an unterminated-quote case is currently tested at tests/core/test_artifact_validation.py:114).
  • src/xagent/core/artifact_validation/formats.py:56-62check_pdf's byte-budget loop calls stream.get_data() without validating the decoded content; corrupting a PDF's FlateDecode payload (structure/xref intact) makes pypdf's decoder swallow the zlib.error and return garbled bytes, so the check reports status="valid" for a page whose content is actually corrupted, and the check's own error message is unreachable for this failure mode. Detect decode failures explicitly (re-raise or check decoded output) and add a corrupted-content-stream test.
  • src/xagent/core/artifact_validation/service.py:56 — the parser subprocess inherits the full host environment (env={**os.environ, "OPENBLAS_NUM_THREADS": "1"}) while being the pipeline's only contact point with untrusted bytes via parser libraries with CVE history (pypdf/Pillow/openpyxl/python-docx/python-pptx), with no isolation beyond an RLIMIT_AS cap — weaker than this codebase's own sandboxed_tool_wrapper.py's _build_execution_env(), which builds a minimal allowlisted env instead of inheriting os.environ. Scrub to an allowlist (PATH, locale, OPENBLAS_NUM_THREADS) to avoid exposing DB/API/OAuth/session secrets to a parser RCE.

Minor

  • src/xagent/core/artifact_validation/service.py (public capacity design) — the single _public_slots slot only guards against public-vs-private starvation; a second concurrent public request is immediately turned to unchecked, and nothing stops one client from issuing unlimited sequential ?validation_only=true requests to hold that slot indefinitely, denying the feature to other anonymous users. Out of scope per the PR's own stated boundary, but worth a follow-up.
  • src/xagent/core/artifact_validation/worker.py:14-17 — the RLIMIT_AS call is unguarded; if a hard RLIMIT_AS below 1GiB is already in force, setrlimit raises unhandled and every validation call fails with a logged exception. Wrap in try/except (ValueError, OSError): pass.
  • src/xagent/core/artifact_validation/formats.py:79-82 — the multi-frame seek/load loop (multi-page TIFF/animated GIF/WEBP) and the newly-advertised .bmp/.tif/.tiff formats have zero test coverage (only .png is exercised). Add at least one multi-frame and one non-PNG raster test.
  • src/xagent/core/file_ref.py:216-247build_workspace_file_ref(validate=True, internal=True) silently performs no validation since the validation code is nested inside if not internal:. No current call site combines both, but the validate docstring doesn't mention this interaction — add a docstring note or a guard.

Blocking: no — recommended event: COMMENT

Comment thread src/xagent/core/artifact_validation/service.py
Comment thread src/xagent/web/api/files.py
Comment thread src/xagent/core/artifact_validation/office.py
Comment thread src/xagent/core/artifact_validation/office.py Outdated
Comment thread src/xagent/core/artifact_validation/registry.py
Comment thread src/xagent/core/artifact_validation/formats.py
Comment thread src/xagent/core/artifact_validation/service.py
Comment thread src/xagent/core/artifact_validation/worker.py
Comment thread src/xagent/core/artifact_validation/formats.py
Comment thread src/xagent/core/file_ref.py
@qinxuye

qinxuye commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Handled the review on 76bd114 in 63008c4; every inline item has an individual reply.

Fixed demonstrated compatibility/reliability gaps: metadata-resolved OOXML main parts, ambiguous CSV verdicts, silently empty standalone FlateDecode recovery, and inherited Linux memory limits. Added renamed/missing-part fixtures for all three Office types and multi-frame/non-PNG coverage. 305 focused backend tests (including 75 artifact-validation tests) and applicable pre-commit checks pass on the current head.

Not expanding scope to authenticated-user/tool scheduling, general throttling, security sandboxing, or wholesale Office-reader fail-open behavior. Those Major labels are not supported by a concrete deployment regression or a legitimate reader-compatible fixture. Registry short-circuiting stays: reader success cannot override a package safety or budget failure. The stated sequential-public-client fairness concern remains separate follow-up work, as the review itself acknowledges.

PDF validation remains bounded reader-based checking, not proof of every decoded stream's semantics or visual fidelity. The concrete silent-empty case is addressed without rejecting successful decoder recovery or introducing a complete PDF decoder/renderer.

No docs files, extra dependencies or service restarts.

@qinxuye
qinxuye requested a review from rogercloud September 6, 2026 14:13

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical

(none)

Major

src/xagent/core/tools/adapters/vibe/sandboxed_tool/sandboxed_tool_wrapper.py:498-502 MCP tool structured_content output never passes through the top-level-only validation strip, and _sanitize_tool_result_value (artifacts.py:239-252) preserves nested validation keys — a malicious MCP server can forge a "valid" verdict straight to the model. Strip validation recursively regardless of nesting, or drop it from SAFE_FILE_REF_KEYS and only re-attach from host-rebuilt refs.

src/xagent/core/artifact_validation/service.py:96,118-122 MemoryError (and other non-OSError) escapes the except OSError guard in _validate_snapshot; artifacts.py:326-334's except Exception: ... continue then silently drops the real generated file from file_refs entirely, and workspace_file_tool.py:364,382 has no guard at all so the same exception turns a successful write into a reported failure. Catch broadly (include MemoryError) and degrade to unchecked instead of dropping/failing.

src/xagent/core/artifact_validation/service.py:29-31 _slots = BoundedSemaphore(2) is a single process-wide pool shared by every authenticated preview and tool-call validation, with no per-user/route limiter (web/api/files.py:256-258); the frontend fires one validation request per attachment (artifact-validation.tsx:48-69) and artifacts.py:323-350 validates every changed file sequentially uncapped — either ordinary pattern can stall all other users' validation requests for minutes. Add a per-user/route concurrency cap ahead of the shared semaphore and bound/parallelize per-call file validation.

src/xagent/web/api/files.py:1783-1784,1851-1858,1887-1894 preview_file never passes public_validation=True even when via_stream_ticket is true, unlike public_preview_file (files.py:2109-2118). Stream-ticket traffic (a shareable capability URL) thus bypasses the public-capacity reservation and consumes private _slots. Pass public_validation=via_stream_ticket.

Minor

src/xagent/core/artifact_validation/defaults.py:14 registers office-reader unconditionally though python-docx/openpyxl/python-pptx are optional extras; registry.py:33-35,61-66 reports .xlsx/.docx/.pptx as supported on a minimal install but every check permanently returns unchecked. Feature-detect the import in supports().

docs/artifact-validation.md was added then deleted in this same PR (commit 9ff135712) with no replacement, leaving the feature undocumented despite the PR description claiming no docs changes.

src/xagent/core/tools/core/image_tool.py:692,832 and pptx_tool.py:2019,2091 don't pass validate=True for registry-supported .png/.pptx outputs, unlike artifacts.py:338/workspace_file_tool.py:364,382, so identical formats get inconsistent validation status depending on producer.

src/xagent/web/README.md:150 documents XAGENT_MAX_UPLOAD_SIZE but omits the two new XAGENT_ARTIFACT_VALIDATION_MAX_BYTES/XAGENT_ARTIFACT_VALIDATION_TIMEOUT_SECONDS vars already present in docker-compose.yml/example.env.

Simplification

L20-82: overengineered ArtifactCheckRegistry class with only one instantiation site (defaults.py:10, a fixed 5-check list). Replace with a module-level tuple of checks plus free supports()/validate() functions.
net: -15 lines possible

Blocking: yes — recommended event: COMMENT

  • sandboxed_tool_wrapper.py:498-502, forged validation verdict reaches model [new]
  • service.py:96,118-122, generated files silently dropped from model's view on exception [new]
  • service.py:29-31, shared validation pool exhaustible by ordinary usage, cross-user stall [new]

Comment thread src/xagent/core/tools/adapters/vibe/sandboxed_tool/sandboxed_tool_wrapper.py Outdated
Comment thread src/xagent/core/artifact_validation/service.py
Comment thread src/xagent/core/artifact_validation/service.py
Comment thread src/xagent/web/api/files.py
Comment thread src/xagent/core/artifact_validation/defaults.py
Comment thread src/xagent/core/artifact_validation/registry.py
@qinxuye

qinxuye commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Review closeout at 02923e8:

Fixed nested guest validation trust, recoverable snapshot-exception isolation, and stream-ticket classification into the public reservation. New tests cover nested structured_content through the wrapper/public sanitizer (including registration failure), MemoryError/RuntimeError during file reads without losing generated attachments, and a real ticket-only validation request. 271 focused tests, Linux-target mypy, and applicable pre-commit checks pass.

The expanded live-sandbox run had 272 passes and 7 failures: all seven failed before tool execution because the test sandbox image cannot import fsspec. This is a validation environment limitation, not presented as a green live-sandbox run; no image/environment changes were made for this PR.

Remaining boundaries are unchanged: no authenticated-user scheduling/QoS subsystem, no runtime dependency-probing API, and no removal of the explicitly requested modular registry. Other producers remain clearly NOT RUN rather than falsely checked; that explicit-state alternative was chosen and documented in the previous review round, so adding synchronous validation to image/PPTX producers is not a new merge prerequisite.

No docs/README additions: the user explicitly excluded docs. The net PR file list contains no docs changes; an earlier add-then-delete in commit history does not alter that final diff. Configuration remains documented in example.env and wired through Compose. This is not grounds to reintroduce the removed documentation.

@qinxuye
qinxuye requested a review from rogercloud September 6, 2026 15:22

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major

  • src/xagent/core/artifact_validation/formats.py:59 — PDF page-count validation is hard-capped at min(content.limits.max_units, 500), a literal constant with no env var or config path to raise it. A legitimate PDF over 500 pages silently skips validation of pages 501+, and the cap is undocumented and untested. Expose max_units via config like max_bytes/timeout already are, or document the ceiling.

Minor

  • src/xagent/core/artifact_validation/office.py:93 — The except tuple omits AttributeError, which python-docx/python-pptx raise on a well-formed-but-semantically-corrupt main part (reproduced with python-docx 1.2.0 / python-pptx 1.0.2). This falls through to the generic checker-bug path and reports unchecked instead of the more precise invalid. Add AttributeError to the caught tuple.
  • src/xagent/core/artifact_validation/formats.py:104 — The PDF except tuple misses pypdf.errors.PyPdfError subclasses that aren't PdfReadError subclasses, e.g. LimitReachedError (reproduced on an oversized FlateDecode stream) and DependencyError. These escape to the generic checker-bug path with a logged traceback instead of a clean unchecked classification. Catch PyPdfError broadly, or add these subclasses explicitly.
  • src/xagent/core/artifact_validation/formats.py:87 — Content-stream decompression via part.get_data() happens before the max_expanded_bytes budget check; the currently-pinned pypdf 6.x has its own 75MB internal limiter masking this, but pyproject.toml:56 only pins pypdf>=4.0.0 with no upper bound. A future resolve to pypdf 4.x/5.x would silently remove that safety net. Pin an upper bound on pypdf, or add an explicit incremental decompression cap independent of the library.
  • src/xagent/core/artifact_validation/formats.py:90decoder.decompress(part._data, 1) reads a private, underscore-prefixed pypdf attribute with no public equivalent. A future pypdf release renaming/removing _data would silently degrade all PDF validation to unchecked via the generic exception path. Wrap this access so an AttributeError here maps to an explicit "pypdf API changed" reason instead of the generic checker-bug path.
  • src/xagent/core/artifact_validation/worker.py:32output.write(json.dumps(...)) on the rebound former-stdout handle has no explicit .flush(). Since sys.stdout was reassigned earlier, CPython's exit-time flush_std_files() no longer covers this object, so the write currently survives only via GC finalization. Add an explicit output.flush() after the write.
  • src/xagent/core/artifact_validation/service.py:132 — The identity(stat: os.stat_result) parameter name shadows the module-level import stat used elsewhere in the file (e.g. stat.S_ISREG). No current bug, but any future line inside this function using stat.* would silently break. Rename the parameter (e.g. stat_result).
  • src/xagent/core/artifact_validation/service.py:145-161 — No in-flight request deduplication: concurrent validation requests for the same not-yet-cached file each spawn their own worker, wasting one of only 2 total parser slots on redundant work. Self-limiting given the 2-slot cap, but worth coalescing concurrent identical requests behind one in-flight computation.
  • frontend/src/components/file/inline-file-preview.tsx:878 — A new wrapper <div data-artifact-validation=...> (not display:contents) now encloses every inline preview where none existed before, with no layout regression test. This could break flex/grid sizing assumptions in parent containers. Worth a quick visual check in the affected preview layouts.
  • src/xagent/config.py:1817-1834 — The max-bytes/timeout getters have no upper bound (only positive+finite is checked), and unlike get_max_upload_size_bytes() (which treats an empty-string env var as "use default"), an empty-string XAGENT_ARTIFACT_VALIDATION_MAX_BYTES raises ValueError and disables validation entirely via a single log warning. Align empty-string handling with the upload-size getter, and consider a sane upper bound.
  • src/xagent/core/tools/artifacts.py:220-223 — The model-facing rendering collapses ~10 distinct unchecked root causes (unsupported format, capacity busy, byte budget exceeded, worker crash, missing dependency, etc.) into one identical status string, discarding the per-check message the API response actually carries. This makes "no validator exists" indistinguishable from "the validator crashed" to the agent. Surface the per-check message (or at least a coarser reason code) in the model-facing text.

Simplification

  • src/xagent/core/artifact_validation/registry.py:20 — yagni: ArtifactCheckRegistry is a full register/lookup class with exactly one production instantiation and one .register() caller (a hardcoded static tuple built once in defaults.py, never varied at runtime). Replace with a plain tuple/list of ArtifactCheck built directly in defaults.py plus free functions supports(checks, filename) / validate(checks, content).
  • frontend/src/components/file/artifact-validation.tsx:16 — shrink: the ['valid','invalid','unchecked'] list is recreated and checked via .includes at 2 separate points. Hoist a single module-level const STATUSES = new Set([...] as const) and reuse it.
    net: -18 lines possible

Blocking: no — recommended event: APPROVE

Comment thread src/xagent/core/artifact_validation/formats.py Outdated
Comment thread src/xagent/core/artifact_validation/office.py
Comment thread src/xagent/core/artifact_validation/formats.py
Comment thread src/xagent/core/artifact_validation/formats.py
Comment thread src/xagent/core/artifact_validation/formats.py Outdated
Comment thread frontend/src/components/file/inline-file-preview.tsx
Comment thread src/xagent/config.py
Comment thread src/xagent/core/tools/artifacts.py
Comment thread src/xagent/core/artifact_validation/registry.py
Comment thread frontend/src/components/file/artifact-validation.tsx Outdated
@qinxuye
qinxuye enabled auto-merge September 6, 2026 17:07
@qinxuye
qinxuye added this pull request to the merge queue Sep 6, 2026
Merged via the queue into xorbitsai:main with commit 1c0ddbc Sep 6, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants