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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docs/RESEARCH-ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` → `<html dir>`), 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]** |
Expand Down
2 changes: 1 addition & 1 deletion docs/accessibility/ACR.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions src/ledger/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 []),
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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"
Expand Down
61 changes: 61 additions & 0 deletions tests/test_metadata_advisory.py
Original file line number Diff line number Diff line change
@@ -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."]
Loading