# Repository — Phase 1 outcome & verification report

**Date:** 2026-06-29
**Branch:** `fix/repo-test-issues-2026-06-29`
**Scope:** Phase 1 of `setup/repository_test_issues_mitigation.md` — upload integrity &
submission correctness (1.1–1.4). Builds on Phase 0 (`repository_phase0_verification_2026_06_29.md`).

All testing in the Docker stack (`docker-compose.yml`), one pytest invocation at a
time. New tests use `force_login`.

---

## A. Changes shipped

| # | Change | Files | Tests |
| --- | --- | --- | --- |
| 1.1 | **Mandatory-field enforcement.** New `services.mandatory_metadata_fields(collection)` is the single source of truth for the required metadata set (collection's `MetadataSchema.mandatory_fields` when configured, else the SRS default `["title","abstract","license_type"]`). `_run_pre_checks` now reads it, and `DocumentSubmitForm.clean()` enforces it *before* ingest — plus `document_type`/`collection`/`license_type` marked `required=True` and a `clean_authors_text` requiring ≥1 parseable author. Template gains `*` markers and per-step inline errors for `document_type`/`license_type`; abstract gets a client-side check. | `documents/services.py`, `documents/forms.py`, `documents/templates/documents/document_submit.html` | `documents/tests/test_mandatory_fields.py` |
| 1.2 | **Collection dropdown empty-state.** When the submitter's eligible collection queryset is empty, the wizard renders an explicit "No collection available" callout instead of a blank `<select>`, and the wizard can't advance past the Collection step (`HAS_COLLECTIONS` JS guard). Collection stays `required`, so submission is blocked server-side too. Public collections are already seeded by `documents_seeder` (theses / policy-briefs / datasets are `public_open`) and the `public_collection` conftest fixture mirrors that — no new seeding needed. | `documents/templates/documents/document_submit.html` | `documents/tests/test_collection_empty_state.py` |
| 1.3 | **SRS-compliant duplicate handling (FRREP-DCI011).** On a hash collision the view pre-scans before creating anything and re-renders the wizard with a resolution panel offering **cancel / new version / separate document**. New hidden `duplicate_decision` field carries the choice: `separate` → `ingest_document(allow_duplicate=True)`; `version` → `services.add_version()` on the matched document (no new Document); `cancel` → abort. Bulk is coherent — the disposition applies per file (each duplicate file becomes a version of the doc it matches, or all are kept separate). | `documents/forms.py`, `documents/views.py`, `documents/templates/documents/document_submit.html` | `documents/tests/test_duplicate_handling.py` |
| 1.4 | **Document-type placement.** Kept `document_type` on step 2 (Bibliographic — it is a bibliographic field) and added a clarifying caption pointing testers to it ("…your collection & licence are on the next step"). Added its inline error block so the wizard's closest-section error-jump lands on step 2. No move, so the JS stage map is unchanged. | `documents/templates/documents/document_submit.html` | covered by `test_mandatory_fields` (document_type-missing re-render) |

### Key decisions
- **`mandatory_metadata_fields` matches the pre-check exactly** (`schema.mandatory_fields if schema else default`) so the form and QA pre-check cannot drift. A configured-but-empty schema list means "no field-level mandatory" for both — but `document_type`, `collection`, and ≥1 author remain enforced by the form independently.
- **`clean_authors_text` does not call `register_author`** — validation must not write Author rows. It only checks for ≥1 line with a non-empty name part; the view parses for real once the form is valid.
- **Cancel is dual-path:** the panel's "Cancel" button dismisses client-side (lets the submitter swap the file without a round-trip); posting `duplicate_decision=cancel` is also handled server-side as an explicit abort (covered by a test).

---

## B. Test results

New Phase 1 tests (22, all green):
- `test_mandatory_fields.py` — form-level required set (incl. schema-driven), parseable-author check, and view-level "missing field → 200, no Document / complete → 302".
- `test_collection_empty_state.py` — populated vs empty dropdown; submission blocked with no collection.
- `test_duplicate_handling.py` — no-decision surfaces the panel; separate / version / cancel branches.

**Suite:** `docker compose exec -T web pytest apps/repository/` → **157 passed, 13 failed**
(was 135 passed / 13 failed before Phase 1 — +22 new passes, baseline failures
unchanged). The 13 are the pre-existing baseline: 11 django-axes `client.login()`
tests (see `feedback_axes_client_login.md`) + 2 `test_ai_insights_live.py` (need a
live `OPENAI_API_KEY`). Same list before and after.

---

## C. Not done in Phase 1 (by design)

Deferred to later phases per the handoff: registry-first author UI (2.1), auto
metadata extraction (2.2), tag autocomplete (2.3), RIMS grant picker (2.4),
auto-classification (2.5), inline reader (4.1), and full per-file bulk
progress/metadata (3.x). No login wall was added to public read surfaces.
