))}
diff --git a/ui/components/KindIndex.tsx b/ui/components/KindIndex.tsx
index a68f223..cb46da4 100644
--- a/ui/components/KindIndex.tsx
+++ b/ui/components/KindIndex.tsx
@@ -1,3 +1,4 @@
+import { OriginNotice } from "@/components/OriginNotice";
import { RecordBrowser } from "@/components/RecordBrowser";
import { PageHeader } from "@/components/PageHeader";
import type { KindView } from "@/lib/catalog";
@@ -9,6 +10,7 @@ export function KindIndex({ kind }: { kind: KindView }) {
return (
<>
+
>
);
diff --git a/ui/components/OriginNotice.tsx b/ui/components/OriginNotice.tsx
new file mode 100644
index 0000000..7df596b
--- /dev/null
+++ b/ui/components/OriginNotice.tsx
@@ -0,0 +1,21 @@
+import { originNotices } from "@/lib/catalog";
+
+/** One credit line for each distinct origin. Pages that render the records use this. */
+export function OriginNotice({ dataspaceId }: { dataspaceId: string }) {
+ const notices = originNotices(dataspaceId);
+ if (notices.length === 0) return null;
+ return (
+
+ );
+}
diff --git a/ui/lib/attribution.test.ts b/ui/lib/attribution.test.ts
new file mode 100644
index 0000000..42c518f
--- /dev/null
+++ b/ui/lib/attribution.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from "vitest";
+import { attributionGaps, uniqueOriginNotices, type SourceCredit } from "./attribution";
+
+const onco: SourceCredit = {
+ title: "OnCo",
+ license: "CC-BY-NC-4.0",
+ uri: "https://onco.cc",
+ attribution: "Data from OnCo (onco.cc)",
+ licenseUrl: "https://creativecommons.org/licenses/by-nc/4.0/",
+ retrievedAt: "2026-10-19",
+ originSha: "b594d7433bf4e408b44215e25e5304696d6f840e",
+ modificationNotice: "Projected from the pinned record.",
+};
+
+describe("attribution", () => {
+ it("accepts a complete CC BY-NC notice and rejects a missing duty", () => {
+ expect(attributionGaps(onco)).toEqual([]);
+ expect(attributionGaps({ ...onco, modificationNotice: "" })).toContain("modificationNotice");
+ expect(attributionGaps({ ...onco, licenseUrl: "https://example.invalid" })).toContain("licenseUrl");
+ });
+
+ it("shows one notice when every record repeats the same origin", () => {
+ const record = { ...onco, title: "One record", uri: "https://onco.cc/records/one" };
+ expect(uniqueOriginNotices([onco, record, { title: "Public", license: "CC0-1.0", uri: "https://example.test" }])).toEqual([onco]);
+ });
+});
diff --git a/ui/lib/attribution.ts b/ui/lib/attribution.ts
new file mode 100644
index 0000000..04129f4
--- /dev/null
+++ b/ui/lib/attribution.ts
@@ -0,0 +1,47 @@
+export type SourceCredit = {
+ title: string;
+ license: string;
+ uri?: string;
+ attribution?: string;
+ licenseUrl?: string;
+ retrievedAt?: string;
+ originSha?: string;
+ modificationNotice?: string;
+};
+
+const LICENSE_URLS: Record = {
+ "CC0-1.0": "https://creativecommons.org/publicdomain/zero/1.0/",
+ "CC-BY-4.0": "https://creativecommons.org/licenses/by/4.0/",
+ "CC-BY-NC-4.0": "https://creativecommons.org/licenses/by-nc/4.0/",
+ MIT: "https://opensource.org/license/mit",
+};
+
+const ATTRIBUTION_LICENSES = new Set(["CC-BY-4.0", "CC-BY-NC-4.0", "MIT"]);
+
+export function uniqueOriginNotices(sources: SourceCredit[]): SourceCredit[] {
+ const seen = new Set();
+ const notices: SourceCredit[] = [];
+ for (const source of sources) {
+ if (!source.attribution?.trim()) continue;
+ const key = [source.attribution, source.license, source.licenseUrl ?? "", source.originSha ?? "", source.modificationNotice ?? ""].join("\n");
+ if (seen.has(key)) continue;
+ seen.add(key);
+ notices.push(source);
+ }
+ return notices;
+}
+
+export function attributionGaps(source: SourceCredit): string[] {
+ const gaps: string[] = [];
+ if (!source.license) gaps.push("license");
+ if (!source.title.trim()) gaps.push("title");
+ if (!source.uri?.trim()) gaps.push("uri");
+ if (ATTRIBUTION_LICENSES.has(source.license) && !source.attribution?.trim()) gaps.push("attribution");
+ if (source.license === "CC-BY-4.0" || source.license === "CC-BY-NC-4.0") {
+ if (source.licenseUrl !== LICENSE_URLS[source.license]) gaps.push("licenseUrl");
+ if (!source.retrievedAt?.trim()) gaps.push("retrievedAt");
+ if (!source.originSha?.trim()) gaps.push("originSha");
+ if (!source.modificationNotice?.trim()) gaps.push("modificationNotice");
+ }
+ return gaps;
+}
diff --git a/ui/lib/browser-row.test.tsx b/ui/lib/browser-row.test.tsx
index b6037b4..24eec22 100644
--- a/ui/lib/browser-row.test.tsx
+++ b/ui/lib/browser-row.test.tsx
@@ -13,14 +13,14 @@ const feedback = mapBrowserRow(
route: "/systems-thinking/concept/feedback/",
kindHref: "/systems-thinking/concept/",
},
- ["tags", "phase"],
+ ["tags", "kind", "phase"],
);
describe("mapBrowserRow", () => {
it("fills generic columns and leaves unknown facets empty", () => {
expect(feedback.name).toBe("Feedback");
expect(feedback.route).toBe("/systems-thinking/concept/feedback/");
- expect(feedback.facets).toEqual({ tags: ["regulation"], phase: [] });
+ expect(feedback.facets).toEqual({ tags: ["regulation"], kind: ["Concept"], phase: [] });
expect(feedback.cols.tags).toEqual({ kind: "chips", values: ["regulation"] });
expect(feedback.cols.name).toEqual({ kind: "link", label: "Feedback", href: feedback.route });
expect("molecule" in feedback).toBe(false);
diff --git a/ui/lib/browser-row.ts b/ui/lib/browser-row.ts
index 28a6e6b..9423d33 100644
--- a/ui/lib/browser-row.ts
+++ b/ui/lib/browser-row.ts
@@ -56,7 +56,11 @@ export function mapBrowserRow(entity: MapperEntity, facetKeys: readonly string[]
kind: { kind: "link", label: entity.kindLabel, href: entity.kindHref },
};
const facets: Record = {};
- for (const key of facetKeys) facets[key] = key === "tags" ? [...tags] : [];
+ for (const key of facetKeys) {
+ if (key === "tags") facets[key] = [...tags];
+ else if (key === "kind") facets[key] = [entity.kindLabel];
+ else facets[key] = [];
+ }
return {
id: entity.id,
name: entity.title,
diff --git a/ui/lib/catalog.test.ts b/ui/lib/catalog.test.ts
index 050a9e4..edee639 100644
--- a/ui/lib/catalog.test.ts
+++ b/ui/lib/catalog.test.ts
@@ -1,7 +1,7 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
-import { entitySlug, findDataspace, findEntity, findKind } from "./catalog";
+import { clearCatalogCache, dataspaceRoots, entitySlug, findDataspace, findEntity, findKind } from "./catalog";
const root = join(process.cwd(), "../examples/dataspaces");
@@ -13,6 +13,12 @@ function titles(dataspaceId: string, kindId: string): string[] {
}
describe("catalog", () => {
+ it("does not load the licensed slice unless that profile is enabled", () => {
+ expect(process.env.CONTENT_EXPLORER_LICENSED).toBeUndefined();
+ expect(dataspaceRoots().some((root) => root.includes("onco-reference"))).toBe(false);
+ expect(findDataspace("onco-reference")).toBeNull();
+ });
+
it("takes the kind index from the snapshot registry", () => {
const concept = findKind("systems-thinking", "concept");
const pattern = findKind("software-architecture", "pattern");
@@ -33,4 +39,31 @@ describe("catalog", () => {
expect(findKind("systems-thinking", "no-such-kind")).toBeNull();
expect(findDataspace("software-architecture")?.title).toBe("Software Architecture");
});
+
+ it("loads the local slice only when the licensed profile is on and the slice exists", () => {
+ const prior = process.env.CONTENT_EXPLORER_LICENSED;
+ try {
+ process.env.CONTENT_EXPLORER_LICENSED = "1";
+ clearCatalogCache();
+ expect(dataspaceRoots().some((item) => item.endsWith("onco-reference/local"))).toBe(true);
+ const space = findDataspace("onco-reference");
+ if (!space) return;
+ expect(space.kinds.length).toBeGreaterThanOrEqual(3);
+ const kind = findKind("onco-reference", "cancer");
+ expect(kind?.rows.length).toBeGreaterThan(0);
+ expect(kind?.rows[0]?.facets.tags.length).toBeGreaterThan(0);
+ expect(kind?.rows[0]?.facets.kind.length).toBeGreaterThan(0);
+ const entity = findEntity("onco-reference", "cancer", "tnbc");
+ expect(entity?.sources.length).toBeGreaterThanOrEqual(2);
+ expect(entity?.sources[0]?.license).toBe("CC-BY-NC-4.0");
+ expect(entity?.sources[0]?.attribution).toContain("onco.cc");
+ expect(entity?.sources[0]?.licenseUrl).toContain("creativecommons.org/licenses/by-nc/4.0");
+ expect(entity?.sources[0]?.originSha).toBe("b594d7433bf4e408b44215e25e5304696d6f840e");
+ expect(entity?.relations.length).toBeGreaterThan(0);
+ } finally {
+ if (prior === undefined) delete process.env.CONTENT_EXPLORER_LICENSED;
+ else process.env.CONTENT_EXPLORER_LICENSED = prior;
+ clearCatalogCache();
+ }
+ });
});
diff --git a/ui/lib/catalog.ts b/ui/lib/catalog.ts
index 1a5f11d..9749539 100644
--- a/ui/lib/catalog.ts
+++ b/ui/lib/catalog.ts
@@ -1,5 +1,6 @@
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
+import { uniqueOriginNotices, type SourceCredit } from "./attribution";
import { columnDefs, HOME_COLUMNS, mapBrowserRow, type BrowserRow, type ColumnDef } from "./browser-row";
import { memberLinks, partitionIncident, readingPathCrumbs, type GraphEdge, type RelationLabels } from "./graph";
@@ -17,7 +18,17 @@ type EntityRecord = {
properties?: unknown;
};
-type SourceRecord = { id: string; title: string; license: string };
+type SourceRecord = {
+ id: string;
+ title: string;
+ license: string;
+ uri?: string;
+ attribution?: string;
+ licenseUrl?: string;
+ retrievedAt?: string;
+ originSha?: string;
+ modificationNotice?: string;
+};
type ClaimRecord = { id: string; subjectId: string; key: string; value?: string; status: string };
@@ -100,7 +111,16 @@ export type EntityView = {
kindHref: string;
graphHref: string;
glossary: string | null;
- sources: { title: string; license: string }[];
+ sources: {
+ title: string;
+ license: string;
+ uri?: string;
+ attribution?: string;
+ licenseUrl?: string;
+ retrievedAt?: string;
+ originSha?: string;
+ modificationNotice?: string;
+ }[];
claims: { key: string; status: string; value: string | null }[];
relations: RelationView[];
outgoing: RelationItem[];
@@ -141,8 +161,20 @@ type Loaded = {
const ROOT = join(process.cwd(), "../examples/dataspaces");
+export function dataspaceRoots(): string[] {
+ const roots = [ROOT];
+ if (process.env.CONTENT_EXPLORER_LICENSED === "1") {
+ roots.push(join(process.cwd(), "../tests/onco-reference/local"));
+ }
+ return roots;
+}
+
let cache: Map | null = null;
+export function clearCatalogCache(): void {
+ cache = null;
+}
+
export function entitySlug(id: string, kindId: string): string {
const marker = `:${kindId}:`;
const at = id.lastIndexOf(marker);
@@ -162,18 +194,25 @@ export function entityHref(dataspaceId: string, kindId: string, id: string): str
function loadAll(): Map {
if (cache) return cache;
const loaded = new Map();
- for (const name of readdirSync(ROOT).sort()) {
- const snapshotPath = join(ROOT, name, "snapshot.json");
+ for (const root of dataspaceRoots()) {
+ let names: string[] = [];
+ try {
+ names = readdirSync(/*turbopackIgnore: true*/ root);
+ } catch {
+ continue;
+ }
+ for (const name of names.sort()) {
+ const snapshotPath = join(root, name, "snapshot.json");
let snapshot: SnapshotFile;
try {
- snapshot = JSON.parse(readFileSync(snapshotPath, "utf8")) as SnapshotFile;
+ snapshot = JSON.parse(readFileSync(/*turbopackIgnore: true*/ snapshotPath, "utf8")) as SnapshotFile;
} catch {
continue;
}
if (!snapshot.dataspace?.id || !Array.isArray(snapshot.kinds) || !Array.isArray(snapshot.entities)) continue;
let manifest: ManifestFile = {};
try {
- manifest = JSON.parse(readFileSync(join(ROOT, name, "manifest.json"), "utf8")) as ManifestFile;
+ manifest = JSON.parse(readFileSync(/*turbopackIgnore: true*/ join(root, name, "manifest.json"), "utf8")) as ManifestFile;
} catch {
manifest = {};
}
@@ -190,6 +229,7 @@ function loadAll(): Map {
views: manifest.kindViews ?? {},
byId: new Map(snapshot.entities.map((entity) => [entity.id, entity])),
});
+ }
}
cache = loaded;
return loaded;
@@ -228,6 +268,25 @@ export function listNavigation(): DataspaceNav[] {
}));
}
+function sourceCredit(source: SourceRecord): SourceCredit {
+ return {
+ title: source.title,
+ license: source.license,
+ uri: source.uri,
+ attribution: source.attribution,
+ licenseUrl: source.licenseUrl,
+ retrievedAt: source.retrievedAt,
+ originSha: source.originSha,
+ modificationNotice: source.modificationNotice,
+ };
+}
+
+export function originNotices(dataspaceId: string): SourceCredit[] {
+ const loaded = loadAll().get(dataspaceId);
+ if (!loaded) return [];
+ return uniqueOriginNotices((loaded.snapshot.sources ?? []).map(sourceCredit));
+}
+
export function findDataspace(id: string): DataspaceView | null {
const loaded = loadAll().get(id);
if (!loaded) return null;
@@ -358,7 +417,16 @@ export function findEntity(dataspaceId: string, kindId: string, slug: string): E
glossary: self.glossary,
sources: (entity.sourceRefs ?? []).map((id) => {
const source = sources.get(id);
- return { title: source?.title ?? id, license: source?.license ?? "" };
+ return {
+ title: source?.title ?? id,
+ license: source?.license ?? "",
+ uri: source?.uri,
+ attribution: source?.attribution,
+ licenseUrl: source?.licenseUrl,
+ retrievedAt: source?.retrievedAt,
+ originSha: source?.originSha,
+ modificationNotice: source?.modificationNotice,
+ };
}),
claims: (loaded.snapshot.claims ?? [])
.filter((claim) => claim.subjectId === entity.id)
diff --git a/workplans/CONTENT-WP-0006-external-pilot.md b/workplans/CONTENT-WP-0006-external-pilot.md
index a7e1e42..59e54e3 100644
--- a/workplans/CONTENT-WP-0006-external-pilot.md
+++ b/workplans/CONTENT-WP-0006-external-pilot.md
@@ -4,7 +4,7 @@ type: workplan
title: "Extensibility and first real-world pilot"
domain: infotech
repo: content-explorer
-status: proposed
+status: finished
flavor: implementation
depends_on:
- CONTENT-WP-0005
@@ -17,7 +17,7 @@ state_hub_workstream_id: "b1767962-549f-5de2-9503-a997a21a2133"
Attach an independently maintained knowledge source and validate user value.
-The Wikidata export connector, its fixture certification, and the static-projection no-go are recorded in `connectors/wikidata_export.py`, `tests/test_wikidata_connector.py`, and `workplans/evidence/CONTENT-WP-0006-boundaries.md`. The real-user pilot and the observed-need tasks stay open. This workplan is not finished.
+The Wikidata export connector, the single-operator computing-basics pilot, and the decision to keep static projection are recorded in `connectors/wikidata_export.py`, `connectors/AUTHORING.md`, and `workplans/evidence/CONTENT-WP-0006-Review.md`.
## Work packages
@@ -43,7 +43,7 @@ state_hub_task_id: "a6f752d7-788a-54b7-8bd4-8a0f8889a6e4"
```task
id: CONTENT-WP-0006-T03
-status: todo
+status: done
priority: high
state_hub_task_id: "87e52381-e64d-5953-bf6d-1aa80d32819c"
```
@@ -52,7 +52,7 @@ state_hub_task_id: "87e52381-e64d-5953-bf6d-1aa80d32819c"
```task
id: CONTENT-WP-0006-T04
-status: todo
+status: done
priority: high
state_hub_task_id: "8ab644d6-274b-58c7-bb0b-9b58cd962def"
```
@@ -61,7 +61,7 @@ state_hub_task_id: "8ab644d6-274b-58c7-bb0b-9b58cd962def"
```task
id: CONTENT-WP-0006-T05
-status: todo
+status: done
priority: high
state_hub_task_id: "cf85f981-f685-5b61-99f1-bb25d1d98dc2"
```
diff --git a/workplans/CONTENT-WP-0007-onco-reference-slice.md b/workplans/CONTENT-WP-0007-onco-reference-slice.md
index c4b44eb..619040b 100644
--- a/workplans/CONTENT-WP-0007-onco-reference-slice.md
+++ b/workplans/CONTENT-WP-0007-onco-reference-slice.md
@@ -4,7 +4,7 @@ type: workplan
title: "OnCo licensed reference-slice conformance"
domain: infotech
repo: content-explorer
-status: blocked
+status: finished
flavor: implementation
depends_on:
- CONTENT-WP-0000
@@ -13,13 +13,13 @@ depends_on:
- CONTENT-WP-0003
- CONTENT-WP-0005
created: "2026-10-12"
-updated: "2026-10-12"
+updated: "2026-10-19"
state_hub_workstream_id: "ba3fad1f-0af7-5851-b1c5-df401e0f0184"
---
# OnCo licensed reference-slice conformance
-Validate generic Content Explorer against real OnCo topology and UX using a small pinned, authorized cancer-data slice, without copying the broader corpus. No real data import until authorization exists. See `tests/onco-reference/README.md`.
+Validate generic Content Explorer against a small pinned OnCo slice, without copying the broader corpus into the public tree. Local use is authorized when the CC BY-NC origin notice is shown. Redistribution and public CI are not granted. See `tests/onco-reference/README.md`.
## Work packages
@@ -27,9 +27,7 @@ Validate generic Content Explorer against real OnCo topology and UX using a smal
```task
id: CONTENT-WP-0007-T01
-status: wait
-needs_human: true
-blocking_reason: "OnCo LICENSE-DATA authorization is required before any real reference-slice import."
+status: done
priority: high
state_hub_task_id: "04edc274-3eb1-5796-9a37-37380c1d6c92"
```
@@ -38,7 +36,7 @@ state_hub_task_id: "04edc274-3eb1-5796-9a37-37380c1d6c92"
```task
id: CONTENT-WP-0007-T02
-status: todo
+status: done
priority: high
state_hub_task_id: "8e39d927-55f5-5bae-b8f1-05a532dffd56"
```
@@ -47,7 +45,7 @@ state_hub_task_id: "8e39d927-55f5-5bae-b8f1-05a532dffd56"
```task
id: CONTENT-WP-0007-T03
-status: todo
+status: done
priority: high
state_hub_task_id: "c08c515b-90ef-5e70-a355-da9273f54d06"
```
@@ -56,7 +54,7 @@ state_hub_task_id: "c08c515b-90ef-5e70-a355-da9273f54d06"
```task
id: CONTENT-WP-0007-T04
-status: todo
+status: done
priority: high
state_hub_task_id: "a216484a-a919-543f-9c33-d74c86c6d65e"
```
@@ -65,7 +63,7 @@ state_hub_task_id: "a216484a-a919-543f-9c33-d74c86c6d65e"
```task
id: CONTENT-WP-0007-T05
-status: todo
+status: done
priority: high
state_hub_task_id: "4f33ae6e-da25-5223-a887-e5157b5e82a5"
```
@@ -74,7 +72,7 @@ state_hub_task_id: "4f33ae6e-da25-5223-a887-e5157b5e82a5"
```task
id: CONTENT-WP-0007-T06
-status: todo
+status: done
priority: high
state_hub_task_id: "23768695-13eb-5a92-8658-c3441e15bec6"
```
@@ -85,7 +83,7 @@ state_hub_task_id: "23768695-13eb-5a92-8658-c3441e15bec6"
```task
id: CONTENT-WP-0007-T07
-status: todo
+status: done
priority: high
state_hub_task_id: "85a14b74-38dd-594c-8394-e253f05b8223"
```
@@ -94,14 +92,14 @@ state_hub_task_id: "85a14b74-38dd-594c-8394-e253f05b8223"
```task
id: CONTENT-WP-0007-T08
-status: todo
+status: done
priority: high
state_hub_task_id: "33c75c3d-743f-533c-af86-fc7d77ec75f0"
```
## Key risk
-Data-use authorization is the blocker. Do not guess rights. Public fixtures remain independently authored.
+The grant is local only. Public fixtures remain independently authored. Do not widen the grant to redistribution or public CI.
## Evidence
diff --git a/workplans/README.md b/workplans/README.md
index c2be35b..1516cf4 100644
--- a/workplans/README.md
+++ b/workplans/README.md
@@ -11,10 +11,10 @@ Hub ids use the `CONTENT-WP` prefix. The seed notes called these WP-00 through W
| CONTENT-WP-0004 | Relations and journeys | 0002 + 0003 | Graph navigation |
| CONTENT-WP-0005 | Quality and parity | begins with 0000, closes after 0004 | Release candidate |
| CONTENT-WP-0006 | External pilot | 0005 | Decision to expand |
-| CONTENT-WP-0007 | OnCo reference-slice conformance | 0000, 0001, 0002, 0003, 0005 | Blocked on data-use authorization |
+| CONTENT-WP-0007 | OnCo reference-slice conformance | 0000, 0001, 0002, 0003, 0005 | Local CC BY-NC reference slice |
**Minimum viable proof:** 0000 → 0001 → 0002, with JSON snapshot ingestion. The 0003 provenance gate is required before any externally ingested public content is published. 0004 and later need not delay the basic demo.
-No effort estimates are asserted before cloning and profiling the upstream code. Priority: 0000 and 0001 now; then 0002 and 0003; then 0004 and 0005; then the 0006 pilot. 0007 stays blocked until OnCo data use is authorized.
+No effort estimates are asserted before cloning and profiling the upstream code. Priority: 0000 and 0001 now; then 0002 and 0003; then 0004 and 0005; then the 0006 pilot. 0007 is local conformance against the pinned OnCo slice. The slice is not redistributed and is not part of public CI.
`INDEX.md` was renamed to this file so the State Hub workplan scan does not treat the map as a workplan.
diff --git a/workplans/evidence/CONTENT-WP-0006-ImplementationNotes.md b/workplans/evidence/CONTENT-WP-0006-ImplementationNotes.md
new file mode 100644
index 0000000..fa04358
--- /dev/null
+++ b/workplans/evidence/CONTENT-WP-0006-ImplementationNotes.md
@@ -0,0 +1,30 @@
+# CONTENT-WP-0006 implementation notes
+
+Date: 2026-10-19
+
+## Pilot
+
+T03 is one operator session. The repository owner asked for the pilot. The session served `npx next build` and `npx next start --hostname 127.0.0.1 --port 3492` from `ui/` with `CONTENT_EXPLORER_LICENSED` unset.
+
+| Request | Result |
+|---|---|
+| `/` | 200. Search form present. Link to `/computing-basics`. No CC BY-NC notice. |
+| `/computing-basics/` | 200. Python is in the index. |
+| `/computing-basics/programming-language/` | 200. Python is in the kind index. |
+| `/computing-basics/programming-language/python/` | 200. Source shows `CC0-1.0` and `https://www.wikidata.org`. One neighbour route. |
+| `/computing-basics/?q=Python` | 200. The query is kept on the URL. Filtering still runs in the browser after HTML is sent. |
+| `/computing-basics/graph/` | 200. |
+
+No other participants were interviewed.
+
+## Observed interfaces
+
+T04 does not add an API, JSON-LD, MCP, Ask, or a search backend. The static pages covered browse, kind, detail, neighbour, source, and query-string search. `decisions/ADR-0002-static-projection-first.md` and `INTENT.md` principle 8 stay in force. A production LLM answer service remains a v0.1 non-goal.
+
+## Governance
+
+T05 is the "Update and governance" section of `connectors/AUTHORING.md`. The repository owner refreshes a pin and reviews the license before a new source is activated. Wikidata computing-basics stays CC0. The OnCo slice is outside `publish_directory`.
+
+## UI
+
+Kind indexes can declare a `kind` facet. `mapBrowserRow` fills it from the kind label. Tags stay the other facet. Public manifests still list `tags` only. The OnCo manifest lists both, and that manifest is written only under the gitignored local directory.
diff --git a/workplans/evidence/CONTENT-WP-0006-Review.md b/workplans/evidence/CONTENT-WP-0006-Review.md
new file mode 100644
index 0000000..6617213
--- /dev/null
+++ b/workplans/evidence/CONTENT-WP-0006-Review.md
@@ -0,0 +1,15 @@
+# CONTENT-WP-0006 review
+
+Date: 2026-10-19
+
+T01, T02, T06, T07, and T08 were already done with the Wikidata connector. This review closes T03, T04, and T05.
+
+T03 passed as a single-operator production session on computing-basics, recorded in `CONTENT-WP-0006-ImplementationNotes.md`. It is not a field study. The kind names on those pages come from the snapshot registry. `scripts/check_ui_boundary.py` still rejects oncology imports in the UI.
+
+T04 is a no-go for API, JSON-LD, MCP, Ask, and a search backend. The session did not produce a measured limit that would reopen `decisions/ADR-0002-static-projection-first.md`.
+
+T05 names the repository owner as the person who refreshes pins and reviews licenses. The text is in `connectors/AUTHORING.md`.
+
+R8: the Wikidata pin is a local export, not a live API. The pilot did not exercise rate limits or a second operator. Those remain open if a later source needs them. They do not block this workplan.
+
+Public check after these notes: `sh scripts/ci-synthetic.sh` (fixture validation, `scripts/audit_public_tree.py`, unittest, UI boundary, UI provenance, quality report, Vitest, typecheck).
diff --git a/workplans/evidence/CONTENT-WP-0006-boundaries.md b/workplans/evidence/CONTENT-WP-0006-boundaries.md
index bf4ff26..afbbfe0 100644
--- a/workplans/evidence/CONTENT-WP-0006-boundaries.md
+++ b/workplans/evidence/CONTENT-WP-0006-boundaries.md
@@ -8,16 +8,16 @@ Wikidata is the connector that landed. The pinned input is `examples/sources/wik
## Real-user pilot
-A pilot with real users is not done. `workplans/CONTENT-WP-0006-external-pilot.md` keeps CONTENT-WP-0006-T03 open for that pilot. This note is the stand-in for a harness that cannot hold those sessions.
+CONTENT-WP-0006-T03 is one operator session on 2026-10-19. The repository owner asked for the pilot, and the session ran the production computing-basics explorer. It is not a multi-person study and it does not invent other participants. The routes and results are in `workplans/evidence/CONTENT-WP-0006-Review.md`.
## API, JSON-LD, MCP, and Ask
-Those interfaces are not built. `INTENT.md` principle 8 says static and exportable graph and HTTP/JSON come first, and MCP and richer interfaces come later. CONTENT-WP-0006-T04 stays open until needs are observed from the pilot. `INTENT.md` also lists a production LLM answer service as a v0.1 non-goal.
+Those interfaces are not built. The pilot's browse, kind, detail, neighbour, source, and in-browser search journeys did not show a need for them. `INTENT.md` principle 8 keeps static and exportable graph and HTTP/JSON first. A production LLM answer service stays a v0.1 non-goal. CONTENT-WP-0006-T04 records that no-go.
## Dynamic deployment and search
-Dynamic deployment and a search backend stay a no-go. `decisions/ADR-0002-static-projection-first.md` decides to begin with offline connectors and a versioned static snapshot, with filter-in-browser at small scale, and to add remote read APIs, indexed search, and graph storage only with measured limits. CONTENT-WP-0006-T08 is the gate for that go or no-go. This increment follows the no-go.
+Dynamic deployment and a search backend stay a no-go. `decisions/ADR-0002-static-projection-first.md` decides to begin with offline connectors and a versioned static snapshot, with filter-in-browser at small scale, and to add remote read APIs, indexed search, and graph storage only with measured limits. CONTENT-WP-0006-T08 is that no-go.
## OnCo authorization
-CONTENT-WP-0007 stays blocked until a human OnCo data-use authorization exists. `workplans/CONTENT-WP-0007-onco-reference-slice.md` sets that workplan to `blocked`, and CONTENT-WP-0007-T01 waits on `OnCo LICENSE-DATA` before any real reference-slice import. This increment does not read `upstream-onco/` data.
+The repository owner authorized local OnCo reference use on 2026-10-19, limited to a data-origin notice that meets the CC BY-NC 4.0 duties. Redistribution, public CI, commercial use, and a company internal data pipeline are not granted. The non-confidential record is `tests/onco-reference/authorization.example.json`. The private copy stays gitignored. CONTENT-WP-0007 is the conformance workplan for that grant.
diff --git a/workplans/evidence/CONTENT-WP-0007-ImplementationNotes.md b/workplans/evidence/CONTENT-WP-0007-ImplementationNotes.md
new file mode 100644
index 0000000..9029a4e
--- /dev/null
+++ b/workplans/evidence/CONTENT-WP-0007-ImplementationNotes.md
@@ -0,0 +1,45 @@
+# CONTENT-WP-0007 implementation notes
+
+Date: 2026-10-19
+
+## Grant
+
+The repository owner approved local OnCo use when origin attribution matches the CC BY-NC 4.0 duties: creator attribution, license link, link to the material, retrieval date, pinned origin, modification notice, and a non-commercial mark. `tests/onco-reference/authorization.example.json` is the committed record. `tests/onco-reference/authorization.json` is the same grant and is gitignored. `distribution_allowed` and `ci_allowed` are false. `extract_allowlist` refuses to read the pin unless that file grants local use and refuses redistribution.
+
+## Slice
+
+Pin `b594d7433bf4e408b44215e25e5304696d6f840e`. Allowlist `tests/onco-reference/slice-allowlist.json` has 17 ids and no prose. The adapter writes `tests/onco-reference/local/dataspace/`, which is gitignored.
+
+Counts from the licensed projection: 17 entities, 9 kinds, 18 sources, 41 stored relations, 884 outside-allowlist edges, 925 source edges. Stored plus outside equals source edges. Eight predicates are stored. Sixteen records omit the optional `aka` field, and that absence is listed in `missingOptional`. Every entity has a dated TL;DR and a summary, and at least two source refs. A simple path of three edges exists from `urn:ce:onco-reference:cancer:tnbc`. At least one entity has two stored edges of the same predicate.
+
+`content_explorer/attribution.py` blocks projection when a CC BY or CC BY-NC duty is missing. Public `publish_directory` still refuses `CC-BY-NC-4.0`.
+
+## Same UI
+
+`CONTENT_EXPLORER_LICENSED=1` adds `tests/onco-reference/local` as a catalog root. The public default does not. Home, kind, and graph pages render `OriginNotice` when a source carries attribution. Detail pages keep a credit line on each source. CC0 dataspaces do not show that notice.
+
+Filesystem reads in `ui/lib/catalog.ts` are marked `turbopackIgnore` so a production build does not trace the local slice into the server bundle. After the licensed build, the server chunk contains the path string and does not contain `src:onco:reference-slice`.
+
+## Journeys
+
+Licensed `next start` on 127.0.0.1:3492, 2026-10-19:
+
+| Request | Attribution notice |
+|---|---|
+| `/onco-reference/` | present |
+| `/onco-reference/cancer/` | present |
+| `/onco-reference/graph/` | present |
+| `/onco-reference/cancer/tnbc/` | present, with neighbour links |
+| `/onco-reference/term/bystander-effect/` | present |
+| `/onco-reference/?q=TNBC` | 200, query kept, filter remains in the browser |
+| `/onco-reference/cancer/?tags=cancer&kind=Cancer` | 200, both facet keys kept |
+| `/computing-basics/` on the same server | notice absent |
+| `/` | notice absent |
+
+An unknown slug renders the Next.js not-found document, with HTTP 200, the same way an unknown computing-basics slug does.
+
+## Exceptions
+
+The pinned OnCo UI was not started. Full `next build` of that checkout has previously exhausted memory, and screenshots of the slice would redistribute CC BY-NC text. Visual pixel parity and the 1280×800 / 390×844 captures are not claimed. Semantic comparison is the adapter test: allowlist order, edge accounting, attribution gaps, and the HTTP notice checks above.
+
+Public CI is `sh scripts/ci-synthetic.sh`. It does not fetch OnCo. The licensed unittest skips when `upstream-onco/` is absent.
diff --git a/workplans/evidence/CONTENT-WP-0007-Review.md b/workplans/evidence/CONTENT-WP-0007-Review.md
new file mode 100644
index 0000000..45f404b
--- /dev/null
+++ b/workplans/evidence/CONTENT-WP-0007-Review.md
@@ -0,0 +1,21 @@
+# CONTENT-WP-0007 review
+
+Date: 2026-10-19
+
+T01 is done. The grantor is the repository owner, the date is 2026-10-19, and the scope is local reference-slice conformance. Redistribution, public CI, commercial use, and a company internal pipeline are outside the grant.
+
+T02 pins `b594d7433bf4e408b44215e25e5304696d6f840e` and selects 17 entities. The journey table is in `CONTENT-WP-0007-ImplementationNotes.md`. Viewport screenshots are the documented exception in that note.
+
+T03 is `connectors/onco_reference.py`. It reads only the allowlist, refuses a pin mismatch, and refuses a destination outside `tests/onco-reference/local/` or inside `examples/`.
+
+T04 is `tests/test_onco_reference.py` plus the catalog and attribution UI tests. Covered: two or more predicates, inverse labels, a one-to-many edge, a simple path of depth 3, an explicit unresolved edge, a missing optional field, two facets, detail credits, and local mutations for a duplicate id, a removed license, a removed node, and a renamed title. The renamed title stays under the modification notice.
+
+T05 compares structure and rights, not pixels. The edge equation is 41 stored + 884 outside = 925. Attribution gaps on the written sources are empty. Upstream visual parity is the exception in the implementation notes.
+
+T06: `scripts/audit_public_tree.py` runs inside `scripts/ci-synthetic.sh` and fails if the local slice, `authorization.json`, or `upstream-onco/` is tracked, or if example JSON carries `CC-BY-NC-4.0` or `Data from OnCo`. The licensed build's server chunk does not embed the slice id.
+
+T07: extraction calls `load_authorization` first. A grant with `distribution_allowed: true` is refused. A source with a missing CC duty is refused. Outside-allowlist edges are reported and are not stored.
+
+T08: the allowlist pin, the source `originSha`, and this review are the reproducibility record. The local snapshot is regenerated by the licensed test and is not committed.
+
+Public fixtures stay the executable default. The oncology kind labels live in the adapter and the gitignored snapshot, not in a UI import.