Keep OnCo provenance headers on adapted UI files.

CONTENT-WP-0002-T08 requires each adapted file to name the pinned commit, the OnCo copyright, and ui/NOTICE.

Assistant: grok
Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
This commit is contained in:
tegwick 2026-10-13 23:32:25 +02:00
parent 9837d43811
commit 940a6a90d0
8 changed files with 178 additions and 3 deletions

View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""Require provenance headers on UI files adapted from the pinned OnCo commit."""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
UI = ROOT / "ui"
PIN_FILE = ROOT / "research" / "baseline" / "ONCO_PIN"
SOURCE_SUFFIXES = {".ts", ".tsx", ".css", ".mjs"}
ADAPTED = ("adapted from OnCo", "reduced from OnCo")
HEADER_MARKS = ("Copyright (c) 2026 OnCo", "MIT License", "ui/NOTICE")
NOTICE_MARKS = (
"Copyright (c) 2026 OnCo",
"MIT License",
"Permission is hereby granted",
"The above copyright notice and this permission notice shall be included",
)
def load_pin(path: Path = PIN_FILE) -> str:
for line in path.read_text(encoding="utf-8").splitlines():
if line.startswith("sha:"):
return line.split(":", 1)[1].strip()
raise ValueError(f"{path} has no sha")
def is_adapted(text: str) -> bool:
folded = text.casefold()
return any(mark.casefold() in folded for mark in ADAPTED)
def header_problems(text: str, label: str, pin: str) -> list[str]:
if not is_adapted(text):
return []
missing = [mark for mark in (*HEADER_MARKS, pin) if mark not in text]
if not missing:
return []
return [f"{label}: provenance header missing {', '.join(missing)}"]
def notice_problems(text: str, pin: str) -> list[str]:
missing = [mark for mark in (*NOTICE_MARKS, pin) if mark not in text]
if not missing:
return []
return [f"ui/NOTICE missing {', '.join(missing)}"]
def footer_problems(text: str, pin: str) -> list[str]:
missing = [mark for mark in ("MIT License", pin) if mark not in text]
if not missing:
return []
return [f"ui/components/SiteFooter.tsx missing {', '.join(missing)}"]
def tree_problems(root: Path = UI, pin: str | None = None) -> list[str]:
pin = load_pin() if pin is None else pin
problems: list[str] = []
for path in sorted(root.rglob("*")):
if not path.is_file() or "node_modules" in path.parts or ".next" in path.parts:
continue
if path.suffix not in SOURCE_SUFFIXES:
continue
problems.extend(header_problems(path.read_text(encoding="utf-8"), str(path.relative_to(ROOT)), pin))
notice = root / "NOTICE"
if not notice.is_file():
problems.append("ui/NOTICE is missing")
else:
problems.extend(notice_problems(notice.read_text(encoding="utf-8"), pin))
footer = root / "components" / "SiteFooter.tsx"
if not footer.is_file():
problems.append("ui/components/SiteFooter.tsx is missing")
else:
problems.extend(footer_problems(footer.read_text(encoding="utf-8"), pin))
return problems
def main() -> int:
problems = tree_problems()
if problems:
print("\n".join(problems))
return 1
print("ui provenance: adapted files name the OnCo pin, copyright, and NOTICE")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,64 @@
"""Adapted UI files keep the OnCo pin, copyright, and NOTICE."""
from __future__ import annotations
import importlib.util
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location("check_ui_provenance", ROOT / "scripts" / "check_ui_provenance.py")
provenance = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(provenance)
PIN = "b594d7433bf4e408b44215e25e5304696d6f840e"
class UiProvenance(unittest.TestCase):
def test_pin_matches_the_baseline_file(self) -> None:
self.assertEqual(provenance.load_pin(), PIN)
def test_rejects_an_adapted_file_without_the_pin(self) -> None:
sample = """
/**
* Adapted from OnCo SkipLink.
* Copyright (c) 2026 OnCo. MIT License. See ui/NOTICE.
*/
"""
problems = provenance.header_problems(sample, "sample", PIN)
self.assertEqual(len(problems), 1)
self.assertIn(PIN, problems[0])
def test_accepts_a_complete_header(self) -> None:
sample = f"""
/*
* Reduced from OnCo src/components/filters/ResultsTable.tsx
* https://github.com/judegomila/OnCo @ {PIN}
* Copyright (c) 2026 OnCo. MIT License. See ui/NOTICE.
*/
"""
self.assertEqual(provenance.header_problems(sample, "sample", PIN), [])
def test_ignores_a_file_that_was_not_copied(self) -> None:
sample = """
/**
* Generic record page.
* OnCo EntityDetail was not copied.
*/
"""
self.assertEqual(provenance.header_problems(sample, "sample", PIN), [])
def test_notice_requires_the_permission_text(self) -> None:
short = f"Copyright (c) 2026 OnCo\nMIT License\n{PIN}\n"
problems = provenance.notice_problems(short, PIN)
self.assertTrue(problems)
self.assertIn("Permission is hereby granted", problems[0])
def test_shipped_ui_keeps_headers_and_notices(self) -> None:
self.assertEqual(provenance.tree_problems(), [])
if __name__ == "__main__":
unittest.main()

View file

@ -1,5 +1,6 @@
/**
* Hero structure adapted from OnCo home: section.hero, kicker, display heading, lede, action.
* https://github.com/judegomila/OnCo @ b594d7433bf4e408b44215e25e5304696d6f840e
* Copyright (c) 2026 OnCo for the adapted pattern. MIT License. See ui/NOTICE.
* The heading and lede are Content Explorer copy.
*/

View file

@ -1,5 +1,6 @@
/**
* Page heading adapted from OnCo PageHeader: kicker, title, lede.
* https://github.com/judegomila/OnCo @ b594d7433bf4e408b44215e25e5304696d6f840e
* Copyright (c) 2026 OnCo for the adapted pattern. MIT License. See ui/NOTICE.
* Garden art and domain logos are not included.
*/

View file

@ -5,6 +5,7 @@ import type { DataspaceNav } from "@/lib/catalog";
/**
* Sticky header shell adapted from OnCo SiteHeader.
* https://github.com/judegomila/OnCo @ b594d7433bf4e408b44215e25e5304696d6f840e
* Copyright (c) 2026 OnCo. MIT License. See ui/NOTICE.
* The OnCo mark and command palette are not included.
* Kind links come from the snapshot registry passed in by the layout.

View file

@ -1,4 +1,9 @@
/** First Tab jumps past the header to <main id="main">. Adapted from OnCo SkipLink; copy is Content Explorer's. */
/**
* First Tab jumps past the header to <main id="main">.
* Adapted from OnCo SkipLink. The link copy is Content Explorer's.
* https://github.com/judegomila/OnCo @ b594d7433bf4e408b44215e25e5304696d6f840e
* Copyright (c) 2026 OnCo. MIT License. See ui/NOTICE.
*/
export function SkipLink() {
return (
<a href="#main" className="skip-link">

View file

@ -16,7 +16,7 @@ state_hub_workstream_id: "55c1d92f-fe1a-552a-998d-2d6905fb6b99"
# UI extraction and experience skeleton
Deliver the strongest OnCo-style interaction patterns on generic data. CONTENT-WP-0000 and CONTENT-WP-0001 are finished, so this plan is eligible. T01 through T07 have landed under `ui/`. T07 rejects an import that names oncology, cancer, drug, or target. T08 is next. See `workplans/evidence/CONTENT-WP-0002-ImplementationNotes.md`.
Deliver the strongest OnCo-style interaction patterns on generic data. CONTENT-WP-0000 and CONTENT-WP-0001 are finished, so this plan is eligible. T01 through T08 have landed under `ui/`. T08 requires each adapted file to name the OnCo pin, the copyright, and `ui/NOTICE`. T09 is next. See `workplans/evidence/CONTENT-WP-0002-ImplementationNotes.md`.
## Work packages
@ -89,7 +89,7 @@ state_hub_task_id: "bd6b4dd8-e897-5c08-9e07-a0246def0faf"
```task
id: CONTENT-WP-0002-T08
status: todo
status: done
priority: high
state_hub_task_id: "16172ffd-23e0-5c4f-8cac-2991e589fcdb"
```

View file

@ -120,3 +120,16 @@ Checks on 2026-10-13, after T07:
- `python3 scripts/check_ui_boundary.py` and `python3 scripts/validate_fixtures.py` passed.
Not done: the provenance gate, or the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T08 — provenance headers and notices
A file under `ui/` that says it was adapted or reduced from OnCo must name the pin in `research/baseline/ONCO_PIN`, `Copyright (c) 2026 OnCo`, `MIT License`, and `ui/NOTICE`. `ui/NOTICE` keeps the MIT permission text and that same pin. The footer keeps the MIT license and the pin in the page text. A file that says OnCo was not copied is not treated as an adapted copy.
SkipLink, SiteHeader, Hero, and PageHeader already named OnCo and the license. They did not name the pin. Those four headers now include `b594d7433bf4e408b44215e25e5304696d6f840e`. ResultsTable, globals.css, browser-row, and table-view already had the full header.
Checks on 2026-10-13, after T08:
- `python3 -m unittest tests.test_ui_provenance tests.test_ui_boundary tests.test_contract tests.test_ingest` passed (21 tests).
- `python3 scripts/check_ui_provenance.py`, `python3 scripts/check_ui_boundary.py`, and `python3 scripts/validate_fixtures.py` passed.
Not done: the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.