Repository navigation
fix(skills): make DocumentAnalysisSkill do the work it reports (#13897) - #14262
Conversation
All three handlers returned {'success': True, 'status': 'queued'} against no
queue, no worker and no consumer. The skill is registered and reachable -
discover_builtin_skills() finds it by module scan, so it is routable with no
textual reference anywhere to grep for - which made the fabricated success worse
than dead code: a live surface reporting work it never did.
extract_text and analyze_document now run real extraction through the canonical
extractor (#13893) and inherit its text-layer detection, so a scanned document
fails with the page numbers that need OCR instead of returning empty success.
The two config fields that changed nothing now change behaviour: max_pages
truncates and reports truncated=True, output_format renders markdown (page
markers), plain (stripped) or json (structured pages). ocr_enabled no longer
defaults to True while no OCR backend exists - it defaults to False, and enabling
it changes the reported reason rather than pretending OCR ran.
summarize_document is NOT backed. It extracts for real and returns success:False
with the text and an explicit statement, rather than passing extraction off as a
summary or restoring the fabricated queue. Wiring it to a backend is #14258 -
that is an architecture decision about how the skill layer reaches an LLM, and
belongs in its own PR.
Paths are validated before any filesystem access, with contains_dotdot_traversal
rather than contains_path_traversal: the latter treats '/' as suspicious because
it guards bare filenames, and would reject every absolute path (#9670).
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
Code reviewReviewed from a sweep of unreviewed PRs. No substantive issues. The work is real and reachable:
Tests are capable of failing: On the failing No |
… boundary (#13897) Code Quality failed on 'Enforce extension/skill/plugin import boundaries (#7372)': a skill may import autobot_shared and its own package only, and utils.path_validation is core-backend. autobot_shared.security.path_validator.validate_path is both boundary-legal and the stronger check. It canonicalizes and then confines the resolved path to the allowed roots, instead of denylisting '..' in the raw string - #14050 records why a raw-string denylist is the wrong test. A path with no '..' in it that still points outside the allowed roots was previously accepted; it is now rejected, and a test covers that case.
…13897) The #7372 boundary check scans every *.py under skills/builtin, tests included, so a test importing the skill it covers trips skill-no-sibling-import. Every other skill test carries the documented waiver for exactly this; this one now matches. Verified with the workflow's own invocation: find ... -name '*.py' -print0 | xargs -0 python3 tools/lint/check_extension_import_boundaries.py exit 0
|
Code Quality fixed. Root cause: Two commits:
Verified with the workflow's own invocation rather than the unit test: Discovered while fixing this: the checker blocks far less than its docstring claims — membership in an explicit list is the whole test, so Test verification for this PR is CI-side. Please treat the |
Thinking Path
I filed this issue claiming the skill was unwired with zero callers. That was
wrong, and the truth is worse — corrected on #13897 before implementing.
SkillRegistry.discover_builtin_skills()scansskills.builtinwithpkgutil.iter_modulesand registers anyBaseSkillsubclass it finds. Thereis no textual reference to grep for; registration is by module scan. Verified by
running it:
So this was not dead code. It was a registered, routable, callable surface
returning fabricated success — three advertised tools reporting completed work
against no queue, no worker and no consumer.
That changes the closure bar. The invariant is not "the handlers now call the
extractor"; it is no handler reports success for work that did not happen.
What Changed
extract_text/analyze_documentrun real extraction through the canonicalextractor (#13893) and inherit its text-layer detection — a scanned document now
fails with the page numbers needing OCR instead of returning empty success.
The two config fields that changed nothing now change behaviour:
max_pagestruncated: trueoutput_formatmarkdown(markers) /plain(stripped) /json(structured pages)ocr_enabledTruewith no OCR anywhereFalse; enabling changes the reported reason rather than pretending OCR ransummarize_documentis deliberately not backed. It extracts for real, thenreturns
success: Falsewith the text and an explicit statement. The alternativeswere passing extraction off as a summary, or restoring the fabricated queue —
both are the defect this PR removes. Wiring it is #14258: no builtin skill
currently reaches an LLM directly, so choosing that coupling is an architecture
decision that belongs in its own PR, not smuggled into this one.
Path validation before any filesystem access. This skill now genuinely reads
what it is handed, so the check precedes the
open(). It usescontains_dotdot_traversal, notcontains_path_traversal— the latter treats/itself as suspicious because it guards bare filenames, and rejected every absolute
path. I hit that in testing (#9670 documents the distinction).
Verification
Mutation-tested:
A fixture correction worth recording. My first fixtures wrote ~3 characters per
page, and every extraction test failed — correctly. The
min_chars_per_pagefloor(50) treats a 3-character page as a page-number stamp, not content (#13884). The
fixtures now write realistic page bodies. The guard was right and my test documents
were the unrealistic thing.
Scope note
tables_attempted(#13895 / PR #14233) andrender_plain(#13894 / PR #14239) arenot used here — both are still unmerged, and I hit import errors reaching for
them. This branch builds against
Dev_new_guionly.plainoutput is thereforestrip_page_markers(render_pages(...)), which is in base.The
document_uploadedtrigger is left in the manifest:manifest.triggersissurfaced only in the routing index and nothing dispatches on it for any skill.
That is a platform-level gap, not this skill's, so removing it here would only
make this one inconsistent with the other 24.
Model Used
Claude Opus 5 (1M context)
Part of #13892 — Wave 3. Does not close #13897 on its own: see #14258.