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:
parent
9837d43811
commit
940a6a90d0
8 changed files with 178 additions and 3 deletions
90
scripts/check_ui_provenance.py
Normal file
90
scripts/check_ui_provenance.py
Normal 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())
|
||||
64
tests/test_ui_provenance.py
Normal file
64
tests/test_ui_provenance.py
Normal 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()
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue