From e2ef79a05b1c0a0e00b52778cc5304659fc94170 Mon Sep 17 00:00:00 2001 From: ChelseaKR <3114598+ChelseaKR@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:26:15 +0000 Subject: [PATCH] feat(ingest): prompt for a description at authoring time (RM8) The ingest CLI already nudged on a missing transcript for audio/video but set only title+publisher on a record's Dublin Core, with no way to supply a description and no prompt when it was absent. Add a repeatable --description flag that populates dublin_core.description, and a non-blocking stderr advisory when a record has none: a description is what makes an item discoverable and gives a reader (including a screen-reader user) context beyond the title. Advisories stay English CLI stderr notes, matching the existing transcript and preservation advisories (no i18n catalog change). Updates the ACR 504 row and the RM8 research-roadmap entry to reflect the new authoring prompt; per-payload alt text for a future image payload type remains the open part of RM8. Tests: a title-only ingest warns 'no description'; --description silences it and persists the value on the record's Dublin Core. Signed-off-by: ChelseaKR <3114598+ChelseaKR@users.noreply.github.com> --- CHANGELOG.md | 4 +++ docs/RESEARCH-ROADMAP.md | 2 +- docs/accessibility/ACR.md | 2 +- src/ledger/cli.py | 19 ++++++++++ tests/test_metadata_advisory.py | 61 +++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 tests/test_metadata_advisory.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 24aa713..5601e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > actually pushed through the release workflow added below. ### Added +- `ledger ingest --description ...` sets a Dublin Core description at authoring + time, and the ingest CLI now nudges (non-blocking) when a record has no + description, alongside the existing missing-transcript advisory — moving the + ACR 504 authoring-tool support forward (RM8). - **Portfolio-standards conformance remediation (2026-07-11).** Closed all five current Tier-1 checker failures with a Python runtime pin, complete CFF metadata, diff --git a/docs/RESEARCH-ROADMAP.md b/docs/RESEARCH-ROADMAP.md index b3dd5cd..8970548 100644 --- a/docs/RESEARCH-ROADMAP.md +++ b/docs/RESEARCH-ROADMAP.md @@ -95,7 +95,7 @@ afternoon · **M** day or two · **L** week+. | RM5 ✅ | **PREMIS Rights entity + per-record citation/provenance block + persistent identifiers** | D1, C1 | P1 | M | [PREMIS v3](https://www.loc.gov/standards/premis/v3/) Rights; prior study T11 (metadata). DC date backfilled, rights/PID thin. **[corroborates research §8 P2 metadata]** + **[NET-NEW PID]** — *shipped: `PremisRights` + ARK PID + PID in citation.* | | RM6 | **Captions/transcripts as a first-class ingest step** for audio/video oral histories | E2, A3, D2 | P1 | M | `ACCESSIBILITY.md` (captions/transcripts "where source allows"); ACR. **[corroborates ACCESSIBILITY]** | | RM7 | ✅ **Shipped.** **More UI languages + RTL + glossed CW tags in every language**, with a community translation path | D3, A2 | P1 | M | Prior §9 shipped en/es; linguistic access flagged under-served. Now ships **en/es/fr/ar** with full CW glosses per language, RTL plumbing (`i18n.text_direction` → ``), and the remaining i18n gates **G1** (UTF-8), **G9** (pseudolocale), **G10** (RTL), **G11** (`Content-Language`/`Vary`), **G12** (CLDR pin) — see `docs/I18N.md`. **[corroborates research §8 P2 i18n]** | -| RM8 | **Authoring-time accessibility prompts** (alt text, captions, minimal metadata) in the ingest/contribute flow | A4, E2 | P2 | M | ACR's `504` row `Partially Supports`: the ingest CLI doesn't prompt for accessibility info. **[corroborates ACR 504]** | +| RM8 | **Authoring-time accessibility prompts** (alt text, captions, minimal metadata) in the ingest/contribute flow | A4, E2 | P2 | M | Partly shipped: the ingest CLI now nudges on a missing transcript (audio/video) and a missing description (`--description`), non-blocking. Remaining: per-payload alt text once an image payload type exists. **[corroborates ACR 504]** | | RM9 | **Onboard a co-maintainer + commission independent security & accessibility audits** | F1, F2 | P0 | L | `CONTINUITY.md` §1/§3: single maintainer, pre-1.0, no independent audit. **[corroborates CONTINUITY]** | | RM10 | ✅ **Scheduled, encrypted off-box backup + key-backup tooling/runbook** (extend `verify-backup`) — **done:** `ledger backup`/`restore-backup` (`backup.py`), `docs/BACKUP-RUNBOOK.md`, cron/timer examples | F3, B1, A5 | P2 | M | `verify-backup` exists (K1); `ADOPTING.md` backup items are manual. **[corroborates ADOPTING]** | | RM11 | ✅ **Committed cadence of manual NVDA/VoiceOver review** (`docs/accessibility/MANUAL-REVIEW-CADENCE.md`) + browser-real **axe CI** over the served site (`tools/a11y_browser/`, `accessibility-browser` job) as engine-backed depth over the static gate (FIX-12). `aria-live` polish on dynamic states still owed. | E2 | P3 | S | ACR: automated gate is a *floor*, manual review periodic. **[corroborates ACCESSIBILITY]** | diff --git a/docs/accessibility/ACR.md b/docs/accessibility/ACR.md index 08eadfb..08f90ae 100644 --- a/docs/accessibility/ACR.md +++ b/docs/accessibility/ACR.md @@ -120,7 +120,7 @@ Criteria introduced in WCAG 2.2 at Levels A and AA. | 502.2.1 User Control of Accessibility Features | Not Applicable | ledger is not a platform and disables no platform accessibility feature. | | 503 Applications | Supports | The browse application uses native controls with correct names, roles, and values; user preferences (zoom, reduced motion) are honoured. | | 503.4 User Controls for Captions and Audio Description | Not Applicable | No media player is provided. | -| 504 Authoring Tools | Partially Supports | The ingest CLI is the authoring path. It accepts structured, accessible metadata (titles, Dublin Core, content warnings) and the renderer produces conformant markup, but the CLI does not yet actively prompt an author to supply accessibility information (e.g. alt text for a future image payload), so authoring-tool support is partial. | +| 504 Authoring Tools | Partially Supports | The ingest CLI is the authoring path. It accepts structured, accessible metadata (titles, Dublin Core description, content warnings, transcripts/captions) and the renderer produces conformant markup. It now actively prompts an author who omits a description (`--description`) or a transcript for an audio/video payload, nudging without blocking (RM8). Per-payload alt text for a future image payload type is not yet modeled, so authoring-tool support remains partial. | ### Revised Section 508 — Chapter 6: Support Documentation and Services diff --git a/src/ledger/cli.py b/src/ledger/cli.py index 65ae0bc..cde3ea5 100644 --- a/src/ledger/cli.py +++ b/src/ledger/cli.py @@ -185,6 +185,7 @@ def _cmd_ingest(args: argparse.Namespace) -> int: # noqa: C901 dublin_core=DublinCore( title=[args.title], publisher=[archive.config.archive_name], + description=list(args.description or []), ), fields=fields, content_warnings=list(args.cw or []), @@ -280,6 +281,17 @@ def _cmd_ingest(args: argparse.Namespace) -> int: # noqa: C901 f"{fmt.recommendation}", file=sys.stderr, ) + # Minimal-metadata advisory: a record with no description is hard to discover + # and gives a reader (including a screen-reader user) no context beyond the + # title. Nudge the author to add one; do not block (RM8; Dublin Core; ACR 504 + # authoring-tool support). + if not record.dublin_core.description: + print( + "note: this record has no description; add one with " + "--description '...' so it is discoverable and gives readers " + "(including screen-reader users) context beyond the title (RM8)", + file=sys.stderr, + ) return 0 @@ -1462,6 +1474,13 @@ def _build_parser() -> argparse.ArgumentParser: p_ingest = sub.add_parser("ingest", help="ingest an item") p_ingest.add_argument("--root", required=True) p_ingest.add_argument("--title", required=True) + p_ingest.add_argument( + "--description", + action="append", + metavar="TEXT", + help="a short Dublin Core description (repeatable); improves discovery and " + "gives readers, including screen-reader users, context (RM8)", + ) p_ingest.add_argument("files", nargs="*", help="payload files to ingest") p_ingest.add_argument( "--public-field", action="append", metavar="name=value", help="a PUBLIC field" diff --git a/tests/test_metadata_advisory.py b/tests/test_metadata_advisory.py new file mode 100644 index 0000000..56cf582 --- /dev/null +++ b/tests/test_metadata_advisory.py @@ -0,0 +1,61 @@ +"""RM8: the ingest CLI nudges an author to supply a description, and the new +``--description`` flag lets them provide it (minimal-metadata authoring support).""" + +from __future__ import annotations + +import glob +from pathlib import Path + +import pytest + +from ledger import cli +from ledger.config import Config +from ledger.ingest import Archive + + +def _init(root: Path) -> None: + assert cli.main(["init", "--root", str(root), "--name", "P"]) == 0 + + +def _ingest(root: Path, payload: Path, *extra: str) -> int: + return cli.main( + [ + "ingest", + "--root", + str(root), + "--title", + "Item", + str(payload), + "--actor", + "s", + "--now", + "2026-01-01T00:00:00Z", + *extra, + ] + ) + + +def test_ingest_without_description_warns( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + root = tmp_path / "arc" + _init(root) + payload = tmp_path / "note.txt" + payload.write_text("hello") + assert _ingest(root, payload) == 0 + assert "no description" in capsys.readouterr().err + + +def test_ingest_with_description_is_quiet_and_persists_it( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + root = tmp_path / "arc" + _init(root) + payload = tmp_path / "note.txt" + payload.write_text("hello") + assert _ingest(root, payload, "--description", "A field recording from the 1994 march.") == 0 + assert "no description" not in capsys.readouterr().err + + archive = Archive(Config.load(root / "store" / "config.json")) + rid = Path(glob.glob(str(root / "store" / "records" / "*.json"))[0]).stem + assert archive.get(rid).dublin_core.description == ["A field recording from the 1994 march."]