Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ child.

03B3B3A merged through PR #233. It installs only the approved `defusedxml`
wheel and adds the shared bounded OPC/OOXML container security capability.
03B3B3B is the active successor. Its bounded DOCX extraction and durable
omission-fact implementation passed focused tests and required internal review;
hosted CI, CodeRabbit, and human merge remain pending. PPTX, XLSX, image, AUTH,
and sufficiency work remain inactive.
03B3B3B merged through PR #234. It adds bounded DOCX extraction and durable
omission facts on the shared OOXML boundary. 03B3B3C implements bounded PPTX
slide/notes extraction and has passed focused deterministic gates and required
internal review; hosted CI, CodeRabbit, and human merge remain pending. XLSX,
image, AUTH, and sufficiency work remain inactive.

AUTH `WS-XINT-002-04B` follows the complete hidden split-03B series and
activates only fixed-service binding and guide read. ART-03C then removes the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ P2
backend/app/modules/artifacts/guide_pptx.py
backend/app/modules/artifacts/guide_extraction.py
backend/app/modules/artifacts/guide_extraction_worker.py
backend/scripts/run_test_lanes.py
backend/tests/test_artifact_architecture.py
backend/tests/test_guide_bindings.py
backend/tests/test_guide_extraction.py
backend/tests/test_guide_pptx.py
backend/tests/fixtures/guide_pptx/**
docs/spec_artifact_storage_service.md
Expand All @@ -44,22 +48,71 @@ DOCX/XLSX behavior, unapproved packages, framework/AUTH/Celery/submission change
## Acceptance criteria

- Require exact PPTX classification and the shared OOXML security boundary.
- Accept exactly 300 slides and reject 301 before extraction. Traverse slides
by presentation order, shapes by XML document order, and text paragraphs/runs
in document order; append speaker notes after their owning slide in notes XML
order. Omit masters, comments, hidden metadata, and embedded objects while
recording omission facts. Exceeding D42's output limit produces unusable
`limit_exceeded`, never partial agent input.
- Use exact policy identity `guide-extraction-v4`; obsolete `unsupported` or
older PPTX evidence cannot replay as current-policy success. The parent
result protocol accepts successful PPTX evidence only with the exact boolean
omission keys `truncated`, `omitted`, `masters`, `comments`,
`hidden_metadata`, `non_text_objects`, and `embedded_objects`.
`truncated=false`; `omitted` is true exactly when any category is true.
Persistence and replay comparison bind the complete omission mapping to the
same canonical output.
- Emit compact sorted JSON with one `slides` array. Each entry has the exact
shape `{"notes":[...],"number":N,"text":[...]}`. `number` is the
one-based presentation position. `text` contains one string per DrawingML
`a:p` in slide shape-tree XML order, with `a:t` run text concatenated in XML
order, `a:tab` represented as tab, and `a:br` represented as newline.
Hyperlink display text and placeholder text remain visible. Grouped shapes
recurse in XML order; table paragraphs follow table row/cell XML order.
Empty text-bearing paragraphs, empty slides, and empty notes remain explicit.
Pictures, charts, diagrams, media, OLE/package objects, alt text, and other
non-text drawing metadata never enter canonical output.
- `notes` uses the same paragraph/run rules and follows notes shape-tree XML
order. Notes placeholders of type `hdr`, `ftr`, `dt`, `sldNum`, and `sldImg`
are metadata and are omitted; body/object notes and ordinary text shapes are
retained. Notes are embedded only in their owning slide entry, never emitted
as an independent slide.
- `ppt/presentation.xml` `p:sldIdLst` is the sole slide-order authority. Resolve
each `r:id` through `ppt/_rels/presentation.xml.rels` using the exact passive
Transitional
`http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide`
or Strict `http://purl.oclc.org/ooxml/officeDocument/relationships/slide`
relationship type. Resolve an optional notes slide through that slide's
`.rels` using the corresponding exact Transitional or Strict `notesSlide`
URI; a package may not mix those relationship namespaces. Resolve
case-variant stored part names through the validator-approved case-folded
map. Missing/dangling/duplicate IDs or targets, cross-root/type mismatches,
one notes part owned by multiple slides, and orphan slide or notes parts fail
`malformed/pptx_relationship_conflict`; they are never silently omitted.
- Accept exactly 300 ordered slides and reject 301 before slide/notes extraction
with `limit_exceeded/pptx_slide_limit`. Missing/unreadable presentation parts
use `malformed/pptx_presentation_unavailable`. Non-well-formed or unsafe XML
is rejected first by the shared boundary as `malformed/ooxml_unsafe_xml`;
well-formed invalid presentation roots use
`malformed/pptx_invalid_presentation_xml`, invalid slide roots use
`malformed/pptx_invalid_slide_xml`, and invalid notes roots use
`malformed/pptx_invalid_notes_xml`. Invalid relationship XML or ownership
remains `malformed/pptx_relationship_conflict`. Reject traversal beyond 64
nested shape/text/container levels as
`malformed/pptx_nesting_limit`. Exceeding D42's exact 4 MiB output limit produces unusable
`limit_exceeded/output_limit`, never partial agent input.
- Successful omission facts record master/handout/notes-master parts,
comment/comment-author parts, hidden document/custom/alt/visibility metadata,
and passive non-text drawing objects. Active embedded content remains a
shared OOXML malformed rejection before PPTX extraction.
- Prove deterministic slide/notes output, unsafe/malformed input, child-only
imports, exact 300/301 and output boundaries, crash, timeout, cancellation,
cleanup, approval-gate, and coverage behavior.
imports, exact relationship/orphan handling, 300/301, depth and output
boundaries, crash, timeout, cancellation, cleanup, approval-gate, complete
cross-process omission facts, v4 persistence/replay identity, and coverage
behavior. Assign the focused PPTX module to the existing canonical hosted
semantic lane without changing lane or coverage policy.

## Verification commands

```bash
(cd backend && uv run ruff check app tests)
(cd backend && python scripts/check_guide_extractor_dependencies.py)
(cd backend && uv run pytest -q tests/test_guide_ooxml.py tests/test_guide_pptx.py tests/test_guide_extraction.py --cov=app.modules.artifacts --cov-report=term-missing --cov-fail-under=90)
(cd backend && uv run pytest -q tests/test_guide_ooxml.py tests/test_guide_pptx.py tests/test_guide_extraction.py tests/test_guide_bindings.py tests/test_artifact_architecture.py tests/test_ci_test_lanes.py)
(cd backend && uv run pytest -q tests/test_guide_pptx.py --cov=app.modules.artifacts.guide_pptx --cov-report=term-missing --cov-fail-under=90)
(metadata_dir="$(mktemp -d)" && trap 'rm -rf "$metadata_dir"' EXIT && (cd backend && WORKSTREAM_TEST_ADMIN_DATABASE_URL=postgresql+asyncpg://workstream:workstream@localhost:5433/postgres .venv/bin/python scripts/run_isolated_tests.py --metadata-json "$metadata_dir/result.json" --timeout-seconds 12600 -- .venv/bin/python -m pytest -q --ignore=tests/test_isolated_database_runner.py --cov=app --cov-report=term-missing --cov-fail-under=78))
python3 scripts/check_stale_artifact_contracts.py
python3 scripts/check_markdown_links.py
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# External Review Response: WS-ART-001-03B3B3C

Reviewed PR: `#235`

Reviewed head: `c3d8fd367d66`

Reviewed at: `2026-07-31`

## Comments addressed

- Threaded the existing shape-tree traversal depth into paragraph text
traversal so the exact 64-level limit cannot reset at an `a:p` boundary.
- Read slide visibility from the parsed `p:sld` root rather than the
presentation's `p:sldId`, and retained `hidden_metadata` provenance without
discarding visible slide text.
- Reused the existing `_replace_member` test helper for mixed namespace, wrong
relationship type, and malformed relationship-root fixtures.
- Added a cross-boundary nesting regression whose outer containers and inner
paragraph nesting are each individually below the limit but exceed 64 in
combination.

## Comments deferred

None.

The CodeRabbit docstring-coverage warning is not a code finding: the hosted
Backend `Docstring coverage` step passed on the reviewed PR head. No coverage
rule or production docstring was changed in response to that stale warning.

## Human decisions needed

None.

## Commands rerun

```text
cd backend
.venv/bin/python -m ruff format app/modules/artifacts/guide_pptx.py tests/test_guide_pptx.py
.venv/bin/python -m ruff check app tests scripts
.venv/bin/python -m pytest -q tests/test_guide_pptx.py \
--cov=app.modules.artifacts.guide_pptx --cov-branch \
--cov-report=term-missing --cov-fail-under=90
```

Result: 21 passed; 94.94 percent branch coverage.

## Remaining risks

Hosted Backend and Agent Gates must rerun on the repair commit. CodeRabbit must
confirm the updated head or leave only resolved/outdated threads before merge.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Internal Review Evidence: WS-ART-001-03B3B3C

Reviewed against trusted main: `a1b3fd0e410d`

Reviewed at: `2026-07-31`

## Candidate

Hidden deterministic PPTX slide and notes extraction on the merged shared
OOXML boundary. The candidate emits bounded canonical content and exact durable
omission facts without activating AUTH, guide sufficiency, XLSX/image work, or
contributor submissions.

## Deterministic Evidence

- Ruff, approved extractor-dependency gate, stale artifact contracts, Markdown
links, lane integrity, and `git diff --check`: PASS;
- focused PPTX adapter suite: 21 passed at 94.43 percent branch coverage;
- focused OOXML, PPTX, extraction, architecture, and lane suite: PASS;
- DB-backed binding/replay tests and repository-wide coverage remain delegated
to the hosted sharded Backend gate; no local full-suite run was used.

## Reviewer Results

| Reviewer | Result | Blocking findings |
|---|---|---|
| architecture | PASS | none |
| security/auth | PASS | none |
| QA/test | PASS WITH LOW RISKS | none |
| senior engineering | PASS | none |
| product/ops | PASS WITH LOW RISKS | none |
| reuse/dedup | PASS WITH LOW RISKS | none |
| CI integrity | PASS | none |
| test delta | PASS | none |
| docs | PASS | none |

## Material Repairs

- restricted canonical text to the exact slide/notes shape tree and explicit
text-body/table capabilities;
- skipped chart, picture, media, and embedded-object subtrees while preserving
bounded omission evidence;
- made every semantic and omission traversal depth bounded, including table
discovery and skipped notes placeholders;
- required exact relationship roots, direct relationship rows, matching
Transitional/Strict families, unique slide and relationship identities, and
exact slide/notes ownership;
- recorded hidden slide visibility and hidden metadata inside omitted subtrees;
- documented shared OOXML malformed-XML precedence and the exact PPTX v4
omission schema.

## Accepted Low Risks

- DB-backed immutable persistence/replay and repository-wide coverage evidence
must come from the hosted Backend gate on the committed PR head.
- DOCX and PPTX currently repeat small adapter-loading and bounded JSON-output
patterns; deduplication should be considered only if XLSX would repeat them.

Valid findings addressed: yes

Open sub-agent sessions: none
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# WS-ART-001-03B3B3C PR Trust Bundle

## Chunk

`WS-ART-001-03B3B3C` — PPTX Extractor (L1).

## Goal and human-approved intent

Add deterministic, bounded PPTX slide and notes extraction after exact PPTX
classification and the merged OOXML security boundary. Preserve original
verified guide bytes as authoritative. This hidden chunk must not activate
AUTH, invoke guide sufficiency, add XLSX/image behavior, or touch contributor
submission ZIPs.

## What changed and why

- Added an isolated PPTX adapter with exact presentation-order slide and notes
extraction, deterministic compact JSON, and fixed omission evidence.
- Advanced PPTX evidence to `guide-extraction-v4` and bound its complete result
schema to immutable persistence and replay checks.
- Reused the worker-owned shared OOXML validator and kept the PPTX adapter
import confined to the isolated child.
- Added the canonical semantic lane, architecture and worker-order proofs,
DB-backed replay cases, and the exact storage-service contract.

## Design chosen and alternatives rejected

The adapter accepts only bytes that pass the shared OOXML boundary. It resolves
the exact presentation relationship graph, traverses only approved shape-tree
text bodies and tables, and returns canonical content plus a fixed omission
map. Rejected alternatives were request-path parsing, direct provider access,
generic ZIP handling, raw binary agent input, broad XML scanning, partial
output, or a second authorization/OOXML protocol.

## Scope control and product behavior

Only hidden guide-source extraction changes. There is no route, authorization
activation, Celery continuation, sufficiency invocation, provider read, guide
binding, submission, checker, review, contribution, payment, or reputation
change. Artifact/parser failures remain bounded internal outcomes rather than
guide-insufficiency decisions.

## Acceptance criteria proof

- Presentation order, grouped shapes, tables, notes ownership, exact paragraph
semantics, Strict/Transitional relationships, and empty structures have
deterministic canonical-output tests.
- Masters, comments, hidden metadata, passive non-text objects, and embedded
objects use an exact boolean omission schema bound to v4 evidence.
- Malformed relationship roots, duplicates, dangling/cross-root targets,
orphans, shared ownership, namespace mixing, and invalid parts fail closed.
- Exactly 300 slides pass; 301 slides, depth beyond 64, and output beyond 4 MiB
fail without partial agent input.
- The isolated runner proves v4 protocol transport and scratch cleanup;
architecture tests prove worker-only adapter imports.
- Persistence tests bind canonical output and complete omission facts and reject
obsolete PPTX policy evidence as a replay target.

## Tests and checks

- Ruff and approved extractor-dependency gate — pass.
- Focused OOXML/PPTX/extraction/architecture/lane suite — pass.
- PPTX module branch coverage — 94.94 percent on the external-review repair
head (21 tests).
- Stale artifact contracts, Markdown links, lane integrity, and
`git diff --check` — pass.
- Hosted Backend/Agent Gates retain DB-backed replay, repository-wide coverage,
and semantic-lane proof; no local full-suite run was used.

## Test delta and CI integrity

No test, assertion, lane, workflow, dependency rule, or coverage threshold was
removed, skipped, or weakened. The new PPTX module joins the existing
`shared_foundations` lane. No dependency or workflow file changed.

## Reviewer results

Architecture, security, senior engineering, CI integrity, test delta, and docs
pass. QA, product/ops, and reuse/dedup pass with only documented low risks. All
blocking traversal, non-text leakage, relationship, visibility, depth, and
contract/test findings were repaired and re-reviewed.

## External review

CodeRabbit identified two correctness gaps and one test-maintainability issue:
depth reset at the paragraph boundary, slide visibility read from `p:sldId`
instead of `p:sld`, and repeated ZIP-fixture mutation code. All three are
repaired with focused regression proof. Its docstring warning was stale; the
hosted Backend docstring gate passed. Hosted checks must rerun on the repair
head before human merge approval.

## Remaining risks and follow-up

DB-backed replay and repository-wide coverage evidence is hosted-only. If the
next XLSX adapter repeats the small OOXML loader or serializer patterns, assess
a bounded shared helper then rather than widening this chunk. XLSX, image,
durable sufficiency continuation, AUTH activation, and legacy cutover remain
separate later chunks.

## Human review focus and merge ownership

Review exact relationship ownership/order, structure-aware text eligibility,
omission semantics, v4 replay identity, depth/output limits, and absence of AUTH
or sufficiency activation. A human owns merge approval; the agent will not
merge this PR.
23 changes: 19 additions & 4 deletions backend/app/modules/artifacts/guide_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
EXTRACTION_POLICY_VERSION = "guide-extraction-v1"
PDF_EXTRACTION_POLICY_VERSION = "guide-extraction-v2"
DOCX_EXTRACTION_POLICY_VERSION = "guide-extraction-v3"
PPTX_EXTRACTION_POLICY_VERSION = "guide-extraction-v4"
EXTRACTOR_VERSION = "1"
MAXIMUM_INPUT_BYTES = 32 * 1024 * 1024
MAXIMUM_OUTPUT_BYTES = 4 * 1024 * 1024
MAXIMUM_PROTOCOL_BYTES = (MAXIMUM_OUTPUT_BYTES * 6) + 1024
WALL_TIMEOUT_SECONDS = 60
_SUPPORTED = frozenset({"plain_text", "markdown", "json", "csv", "pdf", "docx"})
_SUPPORTED = frozenset({"plain_text", "markdown", "json", "csv", "pdf", "docx", "pptx"})
_DEFAULT_OMISSION_FACTS = {"truncated": False, "omitted": False}
_DOCX_OMISSION_KEYS = frozenset(
{
Expand All @@ -36,6 +37,17 @@
"field_instructions",
}
)
_PPTX_OMISSION_KEYS = frozenset(
{
"truncated",
"omitted",
"masters",
"comments",
"hidden_metadata",
"non_text_objects",
"embedded_objects",
}
)


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -166,11 +178,12 @@ def _valid_omission_facts(detected_format: str, status: object, value: object) -
isinstance(key, str) and isinstance(item, bool) for key, item in value.items()
):
return False
if detected_format != "docx" or status != "extracted":
if detected_format not in {"docx", "pptx"} or status != "extracted":
return value == _DEFAULT_OMISSION_FACTS
if frozenset(value) != _DOCX_OMISSION_KEYS or value["truncated"]:
expected_keys = _DOCX_OMISSION_KEYS if detected_format == "docx" else _PPTX_OMISSION_KEYS
if frozenset(value) != expected_keys or value["truncated"]:
return False
categories = _DOCX_OMISSION_KEYS - {"truncated", "omitted"}
categories = expected_keys - {"truncated", "omitted"}
return value["omitted"] == any(value[key] for key in categories)

@staticmethod
Expand Down Expand Up @@ -212,4 +225,6 @@ def extraction_policy_version(detected_format: str) -> str:
return PDF_EXTRACTION_POLICY_VERSION
if detected_format == "docx":
return DOCX_EXTRACTION_POLICY_VERSION
if detected_format == "pptx":
return PPTX_EXTRACTION_POLICY_VERSION
return EXTRACTION_POLICY_VERSION
Loading
Loading