The consolidation plan

Plan and decisions

Rendered directly from the markdown in the repository at build time, so this page cannot drift from the source.

Overview
What DPA is and why the monorepo exists
README.md
Meeting brief
Five-minute status read for AABC, including the recorded feedback
docs/MEETING-BRIEF.md
Decisions
Ten ADRs, each with rejected alternative and cost. Two reopened
docs/DECISIONS.md
Capture protocol
Stage 0: the rubric, the guidance loop, and what a scan does not prove
docs/CAPTURE-PROTOCOL.md
Architecture
The pipeline, the packages, the three numbers
docs/ARCHITECTURE-v0.4.md
Inventory
Assessment of all five upstream systems
docs/INVENTORY.md
Migration
What each repo owner is asked to do
docs/MIGRATION.md
Design system
The template, the tokens, and the tests that stop them drifting
docs/DESIGN-SYSTEM.md
Backlog
Every known gap, merged and prioritised
BACKLOG.md
Attribution
Who wrote what
ATTRIBUTION.md
Contents

DPA — Digital Passport for Artworks

This repository is a working monorepo, not a plan. It consolidates five separate prototypes from the AABC × SDA Bocconi research programme into one pipeline, one passport, one score, and one disclosure model. The code runs, the tests pass, CI is green, and the site is live.

  • Live site: https://ethical-tech-colab.github.io/DPA/
  • Repository: Ethical-Tech-CoLab/DPA — public
  • Tests: 342 passing across the ten tested packages
  • Everything on the site runs on committed fixtures. The scores are real — computed by the real scorer over real cited sources — but no live register was queried and no attestation was written to any chain. See What is real and what is not.

58 is worse than 28

This is the intellectual core of the whole system, so it comes first.

Two objects in the demo score low. The Bura askos scores 58 and is structurally uncovered. The Getty Bronze scores 28 and is well covered. The lower number is the safer object.

  • 28, well covered means the registers that ought to hold this object were able to look, and looked, and found six identifying hits. The number is about the object: records exist and they are consistent.
  • 58, structurally uncovered means no register could ever have named this object. The Bura askos came out of an unrecorded archaeological site; there is no accession, no theft report, nothing a stolen-property register could match. The number measures our blindness, not the object.

A score shown without its coverage class is not just incomplete — it inverts. It would rank the uncoverable object as more trustworthy than the one the registers actually cleared, and it would do this most confidently for exactly the colonial and archaeological material the programme exists to serve. The assess package therefore reports three numbers that are never combined (confidence, coverage, forgery risk), and refuses to let coverage fold into the score. The /coverage route on the live site walks this argument in full.


What DPA is

DPA is the Digital Passport for Artworks: a research programme run by AABC (Ars Pro Mundo) with SDA Bocconi, defined in Annex A — Preliminary Research Framework (February 2026). Its thesis:

An artwork whose provenance is uncertain is currently invisible — it cannot be lent, studied, insured, or claimed, because surfacing it exposes its holder to legal and reputational risk. If a holder could register that object pseudonymously, prove the registration was immutable and time-stamped, and then disclose different amounts of it to different parties — public, museum, enforcement, source community — the object could re-enter scholarship without anyone first having to lose a lawsuit.

A passport here is not a certificate of authenticity and not a title deed. It is a single signed record with as many lawful views as there are roles. The whole system is built so that one record can be shown five ways without the holder having to trust anyone downstream to redact it correctly.


The live site

Eight routes, all statically exported and served from GitHub Pages:

RouteWhat it shows
/Overview: the pipeline, the three numbers, what is real
/demoThe four demo cases, each rendered in a chosen role
/captureStage 0: the scan-quality rubric and the live guidance loop, driven by sliders
/coverageThe "58 is worse than 28" argument, worked over Bura and Getty
/disclosureOne signed record shown to five roles, with a leakage proof
/exhibitA procedural 3D object with role-gated points of interest
/brandLive rebranding, the token contract, and the validator report per theme
/planThese documents, rendered from the repo markdown at build time

The four demo cases and their real generated results:

bura-askos     score 58   structurally-uncovered   human-review   0/9 identifying
benin-bronze   score 48   structurally-uncovered   human-review   0/9 identifying
getty-bronze   score 28   well-covered             human-review   6/9 identifying
schiele-wally  score 28   well-covered             human-review   3/9 identifying

All four route to human review; nothing in the demo auto-issues. That is correct behaviour, not a limitation — see ADR-002 and defect #1 in the meeting brief.


Running it locally

Requires Node 20+ and pnpm. From the repository root:

pnpm install
pnpm fixtures                              # run the pipeline over the 4 cases
pnpm build:web                             # static export to apps/web/out
pnpm --filter @dpa/api start               # request-time API on :8787
pnpm --filter @dpa/agent start bura-askos museum

pnpm fixtures runs the seven-stage pipeline over the four demo cases and writes one JSON file per (passport, role) under apps/web/public/api. It prints the four results above; the output is deterministic, so a clean checkout reproduces the published scores exactly.

The API serves the same records at request time. Role arrives in a header, and there is no authentication (this is deliberate — see the package note below):

curl -H 'X-DPA-Role: museum' localhost:8787/passports/bura-askos

To check the whole workspace:

pnpm -r typecheck        # clean
pnpm -r test             # 342 tests

The packages

Twelve packages under packages/, three apps under apps/. There is no contracts/ directory — the on-chain work was scoped but not built (see BACKLOG.md).

PackageOne honest sentence
schemaThe one passport envelope, the role model, the disclosure tiers, and the single canonical hashing/signing contract (contentHash + signableString) that every other package depends on
captureStage 0. The scan-quality rubric, the real-time guidance loop, and the capture record — the only stage that creates record rather than querying it. Structurally forbidden from touching the provenance score
identityImage fingerprinting (SHA-256 + dHash), similarity and duplicate detection, forensic signals, and Gemini-based object identification
evidenceOne connector interface over eight sources plus tiered register checks — the single evidence service, so nothing else talks to a source directly
assessThe three numbers that are never combined: the accumulation scorer, the coverage model, and forgery risk
issueTwo issuer classes that sign the same bytes — pseudonymous wallet (secp256k1) and accredited institution (ECDSA P-256 cert chain) — plus one verifier
governThe confidentiality envelope: role-based redaction with a checked boundary, and EAS notarisation of the content hash only
lifecycleReview routing (human-review vs auto-issue), StatusList2021 revocation, amendment, and claims
paymentsThe x402 micropayment mechanism for paid register lookups — testnet only, mechanism not economics
pipelineThe composition root: runs the seven stages in order and exposes deliver(passport, role)
fixtures-buildThe four demo cases and the harness that runs the pipeline and writes the site's data
themeEvery visual token in the solution, defined once. Institutions rebrand by configuration; the validator refuses a brand that makes the five roles or three coverage classes hard to tell apart
AppOne honest sentence
apps/webNext.js static export (the live site). Redacts at build time, one JSON per (passport, role), so the disclosure guarantee is physically inspectable on a static host
apps/apinode:http server. Redacts at request time via the X-DPA-Role header. No auth by design — every response says so
apps/agentA CLI that runs the pipeline for one case in one role: start <case-id> [role] [--json]

Both apps/web and apps/api call the same single deliver() code path. The architecture doc explains why both exist: docs/ARCHITECTURE-v0.4.md.


Deploying it under an institution's brand

A museum, a university or a ministry running this will want it to look like theirs, and that must not require a fork. Every visual decision — colour, type scale, corner radius, content width, wordmark, nav height — is a token in packages/theme. apps/web contains no colour literals at all outside its print stylesheet; the CSS custom properties are generated from the theme definitions at build time.

NEXT_PUBLIC_THEME=atrium pnpm build:web

Three brands ship with the repository — slate (default, dark), atrium (light, museum) and campus (high-contrast, larger type for teaching and projection). Adding a fourth is one file:

export const pinacoteca = defineTheme({
  id: "pinacoteca",
  label: "Pinacoteca",
  description: "House brand for a civic picture gallery.",
  colorScheme: "light",
  identity: { wordmark: "Brera", wordmarkAccent: "·", organisation: "Pinacoteca di Brera" },
  brand: { bg: "#fbfaf7", text: "#1b1a17", accent: "#8c2f39" },
});

Anything not overridden falls back to the default, so a brand is usually three or four colours rather than a stylesheet.

Not every token is free, and that is the interesting part. Chrome — background, surfaces, rules, body text, accent — can be anything. The five role colours and three coverage colours cannot, because they carry meaning: they tell a reader which disclosure tier they are looking at and whether the score beside them can be trusted at all. A rebrand that quietly collapses enforcement and public into two similar blues has not restyled the site, it has introduced a disclosure bug wearing a stylesheet — and it will look completely fine to whoever shipped it.

So those eight tokens are themeable but validated. validateTheme checks WCAG contrast and enforces a minimum perceptual separation between role colours, measured in CIELAB rather than by comparing hex values, because hex distance is a poor proxy for whether two colours look different to a person. Every registered theme is checked by a test, so a brand that hurts legibility fails CI instead of shipping. The /brand route renders that report live.

Large-format displays

The places this is meant to run — a gallery kiosk, a Surface Hub in a seminar room, an ultrawide on a registrar's desk — are mostly not laptops. The content frame is therefore a range rather than a fixed width: contentWidth at a laptop and contentWidthWide on a very wide panel, interpolated so that every viewport in between gets a frame proportional to what it actually has. It holds roughly 74–84% of the display at any size, so nothing is ever a narrow column stranded in the middle of a 49" screen.

Widening the frame is not the same as widening the text, and the distinction is enforced. Prose is capped separately by proseMeasure, in ch so it tracks the theme's own type size; tables are capped at the width they were laid out for. The extra space is spent on arrangement — cases side by side, the pipeline in two columns, key/value pairs two-up, the 3D viewer taller, grids gaining columns — because showing more at once is useful and a 2,000px line of text is not. A test asserts that no line of text on any route, in any theme, is longer at 3440px than it is at 1280px.

Type does not grow with viewport width, deliberately. Width cannot tell you viewing distance: an ultrawide at 60cm and a wall panel across a room report the same number and want opposite type sizes. Room-scale deployments select campus, which raises the whole scale explicitly — a decision with a human behind it.


What is real and what is not

This section is load-bearing. The site is a demonstration, and it is honest about being one.

Real. The pipeline runs end to end. The scorer, the coverage model, the redaction boundary, and both signature schemes are the real implementations, exercised by 342 passing tests. The four demo scores are computed by the real scorer over real, cited sources. The redaction is not cosmetic: apps/web writes a physically separate file per role, and assertNoLeakage fails the build if an above-tier field escapes.

Not real.

  • No API keys exist in this repository. The live-evidence and live-identify paths are implemented but throw without keys, and none are committed.
  • No live register was ever contacted. Register checks run over committed fixtures. No result on the site came from INTERPOL, the Art Loss Register, or any real authority.
  • No attestation was written to any chain. Notarisation runs in mock mode. The Base Sepolia constants are real and verified, but nothing was submitted.
  • The demo signing keys are deterministic. They are seeded from a case id so the build reproduces. They must never sign anything of consequence.
  • The 3D geometry is procedural. The exhibit object is generated in code, not a photogrammetry scan of a real artefact.

The stages, in one diagram

0 CAPTURE     object → a record of it   capture: rubric, live guidance, seal
─ ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ──
1 IDENTIFY    image → object            identity: fingerprint, dHash, similarity
2 INVESTIGATE object → sourced claims   evidence: 8 sources + tiered registers
3 ASSESS      claims → three numbers    assess: score, coverage, forgery risk
4 ROUTE       three numbers → decision  lifecycle: human-review vs auto-issue
5 ENVELOPE    → confidentiality tiers   schema/govern: a tier for every field
6 ISSUE       → one signed passport     issue: wallet or institution, same bytes
7 NOTARISE    → content hash on-chain   govern: EAS, hash only, mock by default

Stage 0 was added after AABC asked for it, and the rule above it is not decoration. Stages 1–7 all begin after an object has already been recorded by somebody; where no record was ever created they can measure the hole and nothing more. Capture is the only stage that creates record rather than querying it — which for a structurally-uncovered object like the Bura askos is the only new information the system can generate at all. It is not yet wired into the pipeline; see docs/CAPTURE-PROTOCOL.md.

Redaction is deliberately not a stage. It happens at the delivery boundary in deliver(passport, role), because making it a stage would imply a passport is ever "the redacted one" — when one signed record has as many lawful views as there are roles. The full account is in docs/ARCHITECTURE-v0.4.md.


The ten decisions

Every ADR is now implemented and enforced in code. Each row links to the record, which names the file that enforces it.

#DecisionEnforced in
001One passport envelopepackages/schema/src/passport.ts
002One scorer, accumulation from a floor of 30packages/assess/src/scorer.ts
003Coverage is mandatory and never folded into the scorepackages/assess/src/coverage.ts
004 ⚠️Two issuer classes, one canonicalisation — reopenedpackages/issue/src/{wallet,institution}.ts
005Confidentiality envelope, redacted at the boundarypackages/govern/src/redact.ts
006Notarise the hash onlypackages/govern/src/notarise.ts
007 ⚠️VANGO stays a separate client — reopened(no code vendored)
008One evidence servicepackages/evidence/src/gather.ts
009No register check ever returns "clear"packages/schema verdict enum
010Capture is Stage 0; scan quality never touches the scorepackages/capture/src/assess.ts

Two are reopened by the AABC feedback. ADR-004 has no class for a crowd-sourced contributor who records an object they do not own. ADR-007 settled VANGO's status on the premise that mobile capture was out of scope, which is no longer true. Both are carried to the meeting as open questions rather than defended — the reasoning is in docs/MEETING-BRIEF.md §7.

Consolidation sharpened two of these. ADR-002 and ADR-003 were both changed by defects that only surfaced once the packages were wired together — the score's direction and the coverage model's treatment of never-recorded objects. Those stories are in docs/DECISIONS.md and the meeting brief.


Reading order

If you are…Read
Preparing for the AABC status meetingdocs/MEETING-BRIEF.md
Reviewing the decisionsdocs/DECISIONS.md
Understanding the codedocs/ARCHITECTURE-v0.4.md
Tracing what came from wheredocs/INVENTORY.md
Owning one of the five upstream reposdocs/MIGRATION.md
Touching anything visual — a page, a component, a themedocs/DESIGN-SYSTEM.md
Looking for what is left to doBACKLOG.md

Attribution and licence

Most of packages/ was written elsewhere, by other people, and vendored here; every vendored file carries a PROVENANCE: header, and the full mapping is in ATTRIBUTION.md. VANGO contributes no code by design (ADR-007).

Vendored code retains its original MIT licence. New code in this repository is MIT. Planning and research documents in docs/ are CC BY 4.0.

Contents

Status meeting brief — DPA v0.4

Material for the AABC status and next-steps meeting.

Read time: 5 minutes. Detail is in INVENTORY, DECISIONS and ARCHITECTURE-v0.4.


1. Where the programme actually stands

Five working systems exist. All five run today. Four are public and two are publicly deployed.

Demoable today
Provenance research across 8 sources, from a photo taken on a phoneprovenance-search
Signed provenance passport with per-claim sourcing and paid register checksarts-provenance-agent
Full passport lifecycle — issue, revoke, verify, detect duplicates, catch tamperingdigital-passport-artworks
Visitor-facing multilingual passport with QR stampsVANGO
Role-gated disclosure + on-chain notarisation + 3D exhibityorkerhodes3/dpa-prototype (private)

The programme has moved faster than the framework anticipated. The derivative student work has outrun the reference implementation on substance — evidence gathering, forensics, lifecycle and UX are all more mature in the derivatives than in the original prototype. That is the intended outcome of the framework and it is a good position to be in.

One of these has been through independent academic peer review (arts-provenance-agent, verdict minor revisions). That is unusual for work at this stage and is worth stating plainly.


2. The problem, stated honestly

Five working systems is a good problem, but it is a problem.

  • **The same object can be shown to AABC with three different confidence scores**, depending on which demo is open. The three scoring algorithms disagree and one of them contradicts another inside the same repository — a finding the project's own peer reviewer raised.
  • Four incompatible passport formats. None validate against each other. There is no way to demonstrate the programme as one system.
  • Two different cryptographic trust models.
  • **The framework's central idea — role-gated disclosure — exists in one repo only** (the private one), and has not propagated to any of the work built on top of it.
  • **The programme's best idea — evidence coverage — exists in one other repo only**, and the systems that most need it do not have it.

None of this reflects badly on any individual project. It is the normal result of productive parallel exploration. It does mean the next step cannot be "build more."


3. What v0.4 is

An opinionated consolidation. One pipeline, one passport, one score, one disclosure model — assembled from the best part of each existing system rather than rewritten.

The core insight: the five projects are not competing implementations. They are consecutive stages of one pipeline that nobody had drawn.

IDENTIFY → INVESTIGATE → ASSESS → ISSUE → GOVERN → MAINTAIN → PRESENT
   │            │           │        │        │         │          │
 image ID    8 sources   ONE      ONE      role-    revoke/    web · 3D
 forensics   registers   scorer   envelope gating   amend      · VANGO
 dedup       coverage    +coverage 2 issuer EAS     review
                                  classes  notary   queue

Nine decisions are proposed, each recorded with its rejected alternative and its cost, in DECISIONS.md. The three that matter most:

DecisionWhy it matters to AABC
One scorer — accumulation, from a base of 30Ends the three-number problem. Encodes "nothing is known" as the honest starting point, rather than treating an object as trustworthy until evidence removes trust
Coverage is mandatory and never scoredPrevents the DPA from systematically penalising the exact objects it exists to help
No register check ever returns "clear"Prevents the DPA from becoming a laundering instrument. The strongest negative is no evidence found

4. The two things worth AABC's attention

a. The DPA must never be able to certify an object as clean

Colonial and archaeological material was never inventoried, so it cannot appear in a stolen-property register. A "clear" result would therefore be issued most confidently for exactly the objects most likely to be problematic.

v0.4 makes this structural, not editorial: the permitted verdicts are possible-match, no-evidence-found, not-queryable, not-run. There is no clear. The caveat is signed alongside the verdict so it cannot be stripped downstream.

This is a positioning decision as much as a technical one, and AABC should confirm it is the right posture.

b. A low score means two opposite things, and only coverage can tell them apart

ObjectScoreCoverageWhat it means
Rosetta Stone34/100structurally uncoveredThe registers could never have named this. The score measures our blindness.
Getty Bronze26/100well coveredRecords ought to exist and are thin. The score measures the object.

The lower number is the more informative one. Any interface showing a score without its coverage class is actively misleading — and would mislead most about source-country and colonial material, which is the motivating population of the entire programme.


5. Decisions we need from AABC

#QuestionWhy it cannot be decided by the team
1Who operates the governance root? v0.4 proposes two issuer classes — pseudonymous holders sign for themselves; accredited institutions sign under a CA chain. Somebody must anchor that chain and be able to revoke accreditation.This is an institutional and legal question, not an engineering one
2Is "never clear, only no-evidence-found" the right public posture?It is a reputational and legal position
3Should the framework repo move into the CoLab org? It is currently private under an individual account, which is why its disclosure model did not propagate to the student work built on itOwnership and IP
4What is the priority order: research infrastructure or visitor-facing deployment? This directly determines whether VANGO stays a client (ADR-007) or becomes the primary surfaceStrategic
5Can we get access to a source community for the disclosure-model review? The current model asserts what source communities should and should not see, decided by an implementer with no consultation. It is the weakest claim in the planRequires AABC's relationships
6Is there budget for the register-coverage extension? Getty Provenance Index, German Lost Art Foundation, INTERPOL and source-country authorities. Some are paid; all need validation against known casesBudget

6. What we would do next, if the plan is ratified

Deliberately no v0.4 code has been written. Ratify first.

PhaseWorkRough size
P0Repo owners review this plan; ratify or amend the nine ADRs1 meeting
P1packages/schema — the canonical envelope + fixtures + adapters from all four existing shapesSmall; unblocks everything
P2packages/assess — one scorer + mandatory coverage. Delete the two deduction models. Re-run and republish all quoted scoresSmall code, real writing
P3packages/evidence — merge the two retrieval stacks behind one connector interfaceMedium
P4packages/govern + apps/api — role-parameterised responses, redaction at the boundaryMedium; highest-value
P5packages/issue + packages/lifecycle — two issuer classes, revocationMedium
P6One end-to-end demo object through all seven stages, shown in all four role viewsThe deliverable

The v0.4 demonstration target: one real object, registered pseudonymously, researched across real sources, scored with its coverage class, signed, notarised on-chain, and shown four different ways to four different roles — plus the same object appearing as a stamp in VANGO with no leakage of anything above the public tier.

That single demo would prove the entire framework thesis end to end, which none of the five current systems can do alone.


7. AABC feedback

Status: recorded. This section was empty through v0.4's consolidation build, and was flagged as the most important gap in the document. It is now filled. Everything above §7 was derived from the Annex A framework document and from reading the code — not from AABC's response to the work. What follows is the first recorded feedback from AABC, and §7.3 re-tests all nine ADRs against it.

7.1 Feedback received

Relayed from the AABC meeting. Paraphrased close to the original wording:

Prioritise a protocol for crowd-sourced ingestion of 3D scans of artefacts, which also creates the beginning of a provenance trail.

The approach is similar to the work described to capture provenance in the Manhattan Bridge pedestrian capture protocol. The key difference is that for cultural artefacts we also need photogrammetry via mobile.

We need to help contributors clearly understand how their inputs are rated for scan quality, and in real time show them the adjustments they need to make.

Look at the Bura artefacts — there will be many like that which we would want to scan. Imagine giving this protocol to a museum, having them scan their artefact following the quality guidance and rubric; that scan is then provenance-verified, and ultimately becomes an artefact shown in the 3D exhibit space.

⚠️ One citation in this feedback could not be verified

We searched for the "Manhattan Bridge pedestrian capture protocol" across HAER/Library of Congress records, Starling Lab, Numbers Protocol, the C2PA and Content Authenticity Initiative corpus, and NYC DOT bridge-inspection material. No protocol of that name exists in any public source we could reach.

What does exist, and may be what was meant:

CandidateWhat it actually isFit
Starling Lab / Numbers Protocol StarlingcaptureAnchors a cryptographic "birth certificate" in device hardware — hashes and signs media at capture, before manipulation is possible. Prototyped on the HTC Exodus 1.Closest. It is capture-time provenance sealing performed while walking with a handheld device. If any lead is the referent, it is this one.
HAER NY-127 (Library of Congress)Large-format static photography of the Manhattan Bridge, compiled post-1968, by Jack Boucher.Right bridge, wrong idea. No photogrammetry, no protocol, no provenance sealing.
NYC DOT bridge LiDARReal drone/LiDAR inspection practice; pedestrian-platform LiDAR is discussed in industry generally.No protocol of this name in any procurement or engineering record.

We are not treating this as prior art until the original speaker identifies the source. The design below is grounded in what we could verify — Apple's ObjectCaptureSession, the C2PA specification, and the London Charter — and the open question is carried in BACKLOG P0 §6. This is a question to ask at the meeting, not a gap to paper over.

7.2 What this feedback actually changes

It does not contradict the consolidation thesis. Nothing in §1–§4 is withdrawn. But it re-prioritises, and it opens a hole that no ADR covers.

The pipeline has seven stages and the feedback is about a stage that does not exist. Stages 1–7 (Identify → Investigate → Assess → Issue → Govern → Maintain → Present) all begin after an object has already been recorded by somebody. AABC are asking for the step before that: how the object gets recorded in the first place, by whom, and with what evidentiary value. That is a new Stage 0 — Capture, not a modification of an existing package.

It is aimed precisely at the failure the Bura askos was built to expose. The programme's own fixture states the problem in the object's timeline:

"Niger ratified the 1970 UNESCO Convention… However, the Convention requires a pre-existing inventory — which Bura sites lack."

and

"because Bura funerary sites were not inventoried before looting began, objects from this complex cannot appear in INTERPOL SWOA, the FBI National Stolen Art File, or the Carabinieri TPC archive."

This is why the Bura askos scores 58 and structurally-uncovered — 0 of 9 registers can see it. The coverage model can measure that gap but can do nothing about it, because every register it queries is retrospective: they hold reports of thefts from documented collections. A capture protocol is the only mechanism in the programme that can close the gap rather than report it, by creating the inventory record that the 1970 Convention presupposes and that these sites never had. It cannot do so retroactively for objects already looted. It can do so from today forward for everything still in museum, community, or private custody — which is the population AABC are pointing at with "there will be many like that."

That reframes the programme's value proposition. Stages 1–7 assess objects against a record. Stage 0 creates record where none exists. For structurally-uncovered objects, that is the only new information the system can generate at all.

7.3 Implications for the ADRs

Re-tested individually. Two are genuinely reopened.

ADRVerdictReasoning
001 one envelopeExtendNot contradicted. A capture bundle is a new object the envelope must carry, and it must be expressible in @dpa/schema like everything else. Adding a field is not reopening the decision.
002 one canonical scorerUpheld, with a boundaryScan quality is a new number, and the risk is that it quietly becomes a second scorer of provenance confidence. It must not. One scorer of provenance confidence remains one scorer.
003 coverage never folded into the scoreUpheld and reusedThis is the governing precedent. Scan quality gets exactly the treatment coverage gets: mandatory, reported alongside, never folded in. See ADR-010.
004 two issuer classes⚠️ REOPENEDThe two classes are pseudonymous-wallet (the holder) and accredited-institution. Crowd-sourced ingestion introduces a third actor the model has no slot for: a contributor who scans an object they do not own and cannot make ownership assertions about. A volunteer photographing a museum vitrine is neither class. Treating them as a pseudonymous holder would let a capture masquerade as an ownership claim. This needs a decision before @dpa/capture issues anything.
005 confidentiality envelope is cross-cuttingExtend — and this one is sharpA high-resolution mesh is not a metadata field. It carries two disclosure risks the envelope was never designed for: it is a replication asset (a scan good enough for study is good enough to forge or 3D-print), and for sacred or funerary objects public visibility may itself be the harm. The Bura askos is a funerary vessel. Publishing a rotatable model of grave goods is a source-community consent question, not a checkbox. The envelope extends to capture assets, and the default for a structurally-uncovered funerary object must not be public.
006 notarise the hash, never store on-chainUpheld, reinforcedMeshes are tens of megabytes. Notarise the capture bundle hash; store nothing. The feedback makes this more obviously right, not less.
007 VANGO stays a client⚠️ REOPENEDThis ADR concluded VANGO is a consumer of the public tier and should stay outside the core. But VANGO is a mobile front-end for scanning, and AABC have just made mobile capture a core requirement. The question "does VANGO fold in?" was answered when capture was out of scope. That premise no longer holds and the decision has to be re-argued.
008 one evidence serviceExtendNot contradicted. A capture becomes a new kind of evidence with its own tier, sourced from a device rather than an archive.
009 no register check returns clearUpheld — and it is the most important carry-overThe direct analogue: a good scan must never read as good provenance. A flawless 8K photogrammetry capture of a looted object is a flawless capture of a looted object. Quality of record and legitimacy of holding are orthogonal, and the interface must never let the first be mistaken for the second.

7.4 Consequences for this document

  • §6's phase ordering is now wrong. It was written without a capture stage and sequences work that AABC have implicitly de-prioritised relative to Stage 0. It needs rescoping, not defending.
  • Decision #5 in §5 (source-community disclosure tier, decided with no source-community input) escalates from weak to blocking. It was tolerable while the system only described objects. Capture means the programme will hold photorealistic models of funerary material, and the consent question can no longer be deferred to a later phase.
  • ADR-004 and ADR-007 should be presented as open, not settled. Bringing reopened decisions to the meeting is the point of having recorded the feedback at all.

8. Known weaknesses in our own work — say these before they are found

Stating these first is cheaper than being asked.

WeaknessWhereStatus
Three disagreeing confidence scoresacross reposv0.4 resolves (ADR-002)
Nothing validated against ground truth — unclear whether showcased scores were computed or hand-authoredarts-provenance-agent, raised by its own peer reviewerOpen. Must be closed before any score is shown to AABC
Source list biased toward Western institutions; searches worst where the motivating harm livesarts-provenance-agent, provenance-search — self-declared in bothOpen. P0 in BACKLOG, needs budget
Headline claim "hallucinated history structurally impossible" is retracted later in the same paper. Correct claim: unsourced history is impossiblearts-provenance-agentOpen. Wording fix
Arithmetic error in the one place the reader is invited to check the score by handprovenance-search, self-declaredMoot once ADR-002 lands, but the paper needs rewriting
Source-community disclosure tier decided with no source-community inputdpa-prototypeOpen. Decision #5 above
Duplicate detection is session-only; there is no registrydigital-passport-artworks — README is explicitNeeds persistence in v0.4
HopeOS is a single HTML file (MediaPipe + particle globe), not the haptic/voice SDK the exhibit plan assumeddpa-prototypeIsolated behind IRenderer; exhibit haptics are unbacked
The 3D exhibit artifact is a placeholder sci-fi helmet, not a cultural objectdpa-prototypeNeeds a real scan
The framework repo is private, which is why its disclosure model never propagateddpa-prototypeDecision #3 above
Contents

Architecture decisions — v0.4

Each decision records the conflict, the choice, the alternative rejected, and the cost. None of these are ratified. Any of them may be reopened, and the AABC feedback — now recorded — has been tested against every one.

Two decisions are reopened by that feedback. ADR-004 has no class for a crowd-sourced contributor who scans an object they do not own, and ADR-007 settled VANGO's status on the premise that mobile capture was out of scope, which is no longer true. Both should be presented at the meeting as open questions rather than defended.

Status legend: Proposed · Ratified · Superseded · Reopened


ADR-001 — One passport envelope, derived from arts-provenance-agent

Status: Proposed

Conflict

Four incompatible representations of "a passport":

RepoRepresentation
dpa-prototypeJSON Schema (schemas/dpa.schema.json) + an EAS attestation
arts-provenance-agentZod-typed JSON-LD Verifiable Credential, contentHash + EIP-191 signature
digital-passport-artworksIn-browser record signed by an issuing CA, with a revocation bitstring
VANGOA localStorage stamp — unrelated object, same word

Nothing validates against anything else. There is no way to demonstrate the programme as one system.

Decision

Adopt arts-provenance-agent's schema/passport.ts as the canonical envelope and extend it. It wins because it is the only representation that is simultaneously typed (Zod, so validation is executable rather than documentary), signed over a canonical content hash excluding the signature, carrying evidence provenance per claim (source, sourceType, verifiedBy, tier), and peer-reviewed.

Extensions required for v0.4:

Passport = {
  // --- retained from arts-provenance-agent ---
  "@context", type, id, artwork, provenanceTimeline,
  riskAssessment: { confidenceScore, flags },
  premiumChecks, registryChecks,
  issuer, issuedAt, contentHash, signature,

  // --- NEW: from arts-provenance-agent/src/lib/coverage.ts, promoted (ADR-003) ---
  coverage: {
    class: "well-covered" | "partially-covered" | "structurally-uncovered",
    acquisitionMode: AcquisitionMode,
    registersApplicable: number,
    registersSearched: number,
    rationale: string,
  },

  // --- NEW: from dpa-prototype (ADR-005) ---
  disclosure: {
    envelopeVersion: string,
    fieldTiers: Record<JSONPointer, DisclosureTier>,
    defaultTier: DisclosureTier,
  },

  // --- NEW: from digital-passport-artworks (ADR-004, revocation) ---
  issuerClass: "pseudonymous-wallet" | "accredited-institution",
  credentialStatus: {
    type: "StatusList2021Entry",
    statusListIndex: number,
    statusListCredential: string,
  },

  // --- NEW: from digital-passport-artworks (image forensics) ---
  objectIdentity: {
    sha256: string,
    dHash: string,
    embeddingRef: string | null,
    duplicateOf: string | null,
    forgeryRisk: { score: number, signals: ForensicSignal[] } | null,
  },

  // --- NEW: from dpa-prototype (ADR-006) ---
  notarisation: {
    chain: "base-sepolia",
    easUid: string | null,
    txHash: string | null,
    attestedAt: string | null,
  } | null,
}

Rejected alternative

A new neutral schema designed from scratch. Rejected: it would discard the one artefact that has survived external review, and "design a new format" is how consolidation efforts die.

Cost

Three repos need adapters. digital-passport-artworks in particular currently signs a differently-shaped record, so its signature covers different bytes — this is not a rename, it is a re-issue.


ADR-002 — One canonical scorer: the accumulation model

Status: Proposed

Conflict

Three algorithms produce a "confidence score" on a 0–100 scale, and they disagree about the same object:

WhereModel
arts-provenance-agent/src/agent/assessRisk.tsAccumulate from a base of 30. +18 authoritative source, +8 press, −12 undated early history, −20 for a further gap class
arts-provenance-agent/src/web/pipeline.tsDeduct from 100
provenance-search/server.js computeConfidenceScoreDeduct from 100. −30 per custody gap, −25 if <3 of 7 sources verified, −10 per high-severity flag, −10 anomalous valuation

The agent's own peer review (22 Jul 2026) called this out inside a single repo: "Two disagreeing scoring systems undermine the auditability thesis." Across repos it is worse — the same object can be presented to AABC with three different numbers depending on which demo is open.

Decision

The accumulation model in assessRisk.ts is canonical. The two deduction models are deleted, not deprecated.

Rationale: the deduction models start from an implicit assertion that an object is trustworthy until evidence removes trust. That is exactly backwards for this domain and it is the same error the coverage model exists to correct — an object with no published history is not a 100 waiting to be reduced, it is a low score that evidence has not yet raised. The accumulation model's base of 30 encodes "nothing is known," which is the honest starting position.

Secondary rationale: the peer review already designated it, and re-litigating a decision an external referee endorsed is unforced.

Rejected alternatives

  • Keep both and show both. Rejected: two numbers is the defect, not the fix.
  • Average them. Rejected: an average of two models with different semantics has no meaning at all.
  • Deduction model, because 100 is more intuitive to a lay reader. Rejected on the substantive grounds above. Intuitive-but-wrong is the worse failure here.

Cost

provenance-search's displayed scores all change. Its research paper cites specific scores and will need re-running and re-writing. The paper's known arithmetic error becomes moot rather than fixed.

Required follow-up

The peer review's finding that nothing was validated against ground truth remains open. Before v0.4 shows a score to AABC, run the canonical scorer over the hand-researched catalogue objects and publish machine scores beside the hand-authored narratives, labelled as to which is which.


ADR-003 — Coverage is mandatory, and is never folded into the score

Status: Proposed

Decision

arts-provenance-agent/src/lib/coverage.ts is promoted from one repo's internal module to a required, signed field of every passport and a required element of every UI that displays a score.

Two hard rules:

  1. Coverage never adjusts the confidence score. Folding it in would produce one number meaning two things, which is the defect being fixed.
  2. A score is only comparable to other scores in the same coverage class. Any UI that ranks, sorts, or compares scores across coverage classes is incorrect, and any score rendered without its coverage class is a bug.

Why this is the most important decision in the plan

The confidence score is mechanically a count of how much published evidence was found. Where the documentary record is dense, a hole in it is itself evidence. Where no record was ever created — colonial acquisitions, unexcavated archaeological material, objects taken from communities that no institution inventoried — finding nothing tells you nothing.

Both produce the same low number, and the low-coverage case is the motivating population of this entire programme. A DPA that scores naively would systematically penalise exactly the objects it exists to bring into the light. The worked example already in the codebase: the Rosetta Stone scores 34/100 as structurally uncovered; the Getty Bronze scores 26/100 as well covered. The lower number is the more informative one. Without the coverage class those two results are indistinguishable and both readings are wrong.

Cost

Every score-rendering surface in every repo changes. This is the largest UI change in the consolidation, and it is non-negotiable.


ADR-004 — Two issuer classes, not one trust model

Status: Reopened by the AABC capture feedback — see MEETING-BRIEF §7.3. Both classes assert something about ownership. Crowd-sourced capture introduces an actor that asserts nothing about ownership and still needs to be represented: a contributor who records an object they do not own. @dpa/capture works around this for now with a separate CaptureOperatorRole enum, deliberately not reusing IssuerClass, so that a capture cannot be read as a title claim. That is a containment measure, not an answer.

Conflict

RepoModel
arts-provenance-agentdid:pkh · secp256k1 · EIP-191 over keccak256. The wallet is the PKI. Decentralised, pseudonymous, self-sovereign
digital-passport-artworksECDSA P-256 · Web Crypto · governance root → federated root → issuing CA. Institutional accreditation. Hierarchical, revocable, auditable

These are genuinely different trust architectures. Curves and signature formats differ; more importantly the meaning of a signature differs.

Decision

Support both, as two issuer classes over one envelope.

ClassSigns withMeansServes
pseudonymous-walletsecp256k1 / EIP-191, did:pkh"An identified-but-unnamed party asserts this, and cannot later deny it"The holder registering an uncertain object without exposing identity
accredited-institutionECDSA P-256, CA chain to a governance root"An accredited body vouches for this, and its accreditation can be revoked"Museums, registries, and the AABC governance layer

issuerClass is a discriminator on the envelope. Verifiers select the verification path from it. Both classes carry the same credentialStatus revocation entry.

Why not pick one

Picking one breaks the framework. Requiring institutional accreditation destroys the pseudonymity the whole thesis depends on — a holder who must be named will not register. Requiring a self-signed wallet destroys institutional trust — a museum cannot rely on an anonymous assertion, and enforcement certainly cannot.

The framework needs both parties in the same registry, which means both signature meanings must coexist. What was read as a conflict is actually the two halves of the requirement, discovered independently by two teams.

Cost

Two verification paths, two key-management stories, and a real governance question — who operates the governance root? — that is a policy decision for AABC, not an engineering one. Flagged in the meeting brief.


ADR-005 — The confidentiality envelope becomes cross-cutting

Status: Proposed

Decision

Role-gated disclosure moves from a single repo's rendering concern to a property of the passport itself. Every field carries a disclosure tier; redaction happens at the API boundary, not in the view layer.

Roles:

public  <  museum  <  enforcement  <  owner        (linear)

source-community                                    (ORTHOGONAL)
   sees: public fields + source-community fields
   never sees: museum internals, enforcement internals

claimStatus and custodianship are promoted into the source-community tier, so a community can see claims made on its own objects.

Why it must be cross-cutting

The framework's participation model depends on it. A holder registers only if the disclosure boundary is credible before they submit. If redaction is a view-layer concern, then every new client is a new opportunity to leak, and the boundary is only as strong as the least careful frontend. Signing the field→tier map into the passport makes the boundary an auditable property of the record.

It was chosen under time pressure by a single implementer with no external input, and it encodes a substantive claim — that source communities should see claim status but not enforcement internals — that no source community has been consulted about. It is defensible and it may well be wrong.

This is the item most in need of the Phase 2 stakeholder co-design work the prototype deliberately skipped. It is carried as a first-class open question in the backlog and should be raised explicitly with AABC.

Cost

Every API response becomes role-parameterised. Caching gets harder. The fieldTiers map must be maintained in lockstep with the schema, which argues for generating one from the other.


ADR-006 — Notarise, never store, on-chain

Status: Proposed (carried unchanged from dpa-prototype)

Decision

Keep the existing spine:

ChainBase Sepolia · chainId 84532 · https://sepolia.base.org
EAS0x4200000000000000000000000000000000000021
SchemaRegistry0x4200000000000000000000000000000000000020
Explorerhttps://base-sepolia.easscan.org
Schemabytes32 contentHash, string passportId, uint8 confidentialityLevel

Only the content hash is attested. No metadata, no images, no PII, no provenance text goes on-chain — ever.

Note for implementers: the Ethereum-Sepolia EAS address 0xC2679fBD… is empty on Base Sepolia. Do not reuse it. These addresses were verified by eth_getCode.

Why

On-chain immutability is a feature for proof of registration time and a catastrophe for content. GDPR erasure, incorrect claims, and the disclosure model are all incompatible with permanent public content. Attesting a hash gives the immutability the thesis needs and keeps every mutable and confidential concern off-chain where it can be corrected and gated.

x402 payment settlement (from arts-provenance-agent) is a separate on-chain concern on the same network and is unaffected.

Cost

Off-chain availability becomes the weak link — a lost record makes its attestation unverifiable. Storage durability is an open backlog item.


ADR-007 — VANGO stays a client; it is not folded into the core

Status: Reopened by the AABC capture feedback — see MEETING-BRIEF §7.3.

This ADR concluded that VANGO belongs outside the core because it is a consumer of passports rather than a producer of them. That reasoning was sound on the premise that capture was out of scope. AABC have now made mobile capture a core requirement, and VANGO is a mobile front-end for scanning — it is the one codebase in the programme already solving the phone-camera problem. The question "does VANGO fold in?" has to be re-argued from the new premise, and the answer may now be different. The decision below is retained unchanged so the original reasoning can be examined rather than quietly rewritten.

Decision

VANGO is not merged into the v0.4 core. It becomes the reference consumer of the public disclosure tier and remains its own repository, its own release cycle, and its own team.

Why not fold it in

  1. Different concern. VANGO answers *"how does a visitor engage with art across venues?"* The core answers *"can an object with uncertain provenance be safely surfaced?"* Merging them would subordinate a good consumer product to a research instrument and slow both.
  2. It would sit inside the trust boundary without being trustworthy. VANGO deliberately removed authentication and stores stamps in localStorage. That is correct for a stamp collection and disqualifying for anything inside the passport trust core.
  3. The word "passport" collides. A VANGO passport belongs to a visitor and collects venues. A DPA passport belongs to an object and collects evidence. Same word, opposite subject. Merging the codebases would make the collision permanent instead of fixable.
  4. Its value is highest as a demonstration. VANGO consuming the public tier proves something no internal document can: that the confidentiality envelope is strong enough to drive a real public product with no leakage. It is a live test of ADR-005.

Why it is nonetheless strategically important

It is the only consumer-facing surface in the programme, the only multilingual one (EN/FR/IT — matching the actual stakeholder languages), and the only physical→digital bridge via QR signage. It is the visible end of "from shadow to light": the point at which a passport-shielded object is encountered by an ordinary visitor. Its catalogue already contains BURA01 — Bura Ceramics, Niger, AABC — which is exactly the class of object the framework is about.

What is asked of VANGO

  1. Resolve the naming collision (e.g. "visitor passport" vs. "object passport") before any joint demonstration.
  2. Read its CATALOG from the v0.4 public read API rather than a hardcoded object in src/App.jsx.
  3. Where a stamped artwork has a DPA passport, deep-link to its public view.

None of these require merging repositories.

Revisit if

AABC's feedback prioritises visitor-facing deployment over research infrastructure. In that case VANGO becomes the primary surface and this decision should be reopened.


ADR-008 — One evidence service

Status: Proposed

Conflict

arts-provenance-agent and provenance-search both integrate Tavily, Wikidata SPARQL and museum APIs, with different allow-lists, different result shapes and different caveats. Two teams maintain two answers to the same question.

Decision

One evidence service, built on arts-provenance-agent's tool layer (src/tools/), absorbing provenance-search's connectors:

Retained fromWhat
agentTavily grounding · Wikidata SPARQL · tiered register checks with signed access/verdict/caveat · x402 paid checks
provenance-searchThe Met · Art Institute of Chicago · MoMA (bundled static export — live API is Cloudflare-blocked) · Wikipedia · Europeana · Gemini Vision identification

Two patterns from provenance-search are generalised, not just ported:

  • The general-knowledge fallback. Answering from model knowledge is permitted, but the result must be labelled isGeneralKnowledge: true, must never be marked verified, and must auto-attach a medium-severity flag. This becomes the required treatment of any unsourced assertion system-wide.
  • The bundled static export. Where a source blocks programmatic access, shipping a dated snapshot is preferred to dropping the source — provided the snapshot date is recorded in the passport.

Carried over as an open problem, not solved by this merge

The source bias both repos declare. The permitted-source list is predominantly Western institutions plus one commercial register, which searches worst exactly where the motivating harm lives. Adding the Getty Provenance Index, the German Lost Art Foundation, INTERPOL and source-country heritage authorities is named in both repos' backlogs as the first substantive extension and is carried into the v0.4 backlog as a P0.

The merge does not fix this. It is important that the meeting says so.


ADR-009 — "No register check returns clear" is normative programme-wide

Status: Proposed

Decision

Adopt arts-provenance-agent's register verdict enum across the programme. The permitted verdicts are:

possible-match  |  no-evidence-found  |  not-queryable  |  not-run

There is no clear, no clean, and no verified-not-stolen. The strongest available negative result is no-evidence-found.

Further, access, verdict, method, caveat and officialSearch are carried together and signed, so a downstream consumer cannot strip a verdict from the caveat that qualifies it.

Why

Colonial and archaeological material was never inventoried and therefore cannot appear in a stolen-property register. A "clear" result would be a false assurance produced by the structural absence of a record — and it would be issued most confidently for exactly the objects most likely to be problematic.

This also protects the programme. A DPA that could be read as certifying an object as clean would become a laundering instrument, which is the precise failure mode that would end it.

Only Wikidata is genuinely machine-queryable. INTERPOL, the FBI NSAF, the Carabinieri TPC, the German Lost Art Foundation and the Getty Provenance Index are domain-scoped search or human referral only, and every passport must say so about itself.

Cost

Users want a green tick and will not get one. Every UI must communicate a three-way state — lead found / nothing found / could not look — where users expect two.


ADR-010 — Capture is Stage 0, and capture quality is a third independent number

Status: Proposed · the first ADR derived from AABC feedback rather than from Annex A and the code

Conflict

The pipeline has seven stages and every one of them begins after an object has already been recorded by somebody. @dpa/evidence searches archives, @dpa/assess scores what was found, @dpa/govern notarises the result. Where no record was ever created, all seven stages can do is measure the hole: ADR-003 exists precisely to stop that hole being misread as a clean result.

AABC have asked for the step before all of this — how an object gets recorded in the first place, by whom, and with what evidentiary value.

Decision

1. Capture becomes Stage 0, implemented as @dpa/capture, positioned before Identify. It is a new position in the pipeline rather than a modification of an existing package, because nothing that exists takes a physical object as input.

2. Capture quality is a third independent number. qualityScore is reported alongside confidenceScore and coverage, and is never folded into either. Structurally enforced: @dpa/capture must not import @dpa/assess and @dpa/assess must not import @dpa/capture. A test asserts this at the module boundary.

3. Quality is bounded by its weakest dimension, not averaged. The class is the minimum across ten metrics. Four hundred photographs that are all out of focus produce an out-of-focus mesh; averaging perfect image count against poor sharpness would report that capture as acceptable.

4. A capture record must state what it does not prove. attests and doesNotAttest are mandatory schema fields, and the builder refuses to emit a record whose doesNotAttest omits the legitimacy disclaimer.

5. Capture assets never default to public, and default to source-community for funerary and sacred material.

Why quality must not touch the confidence score

This is ADR-003 applied to a new axis, and the failure it prevents is sharper here.

If capture quality contributed to provenance confidence, a museum could raise an object's provenance confidence by buying a better camera. An institution with a well-funded imaging department and no provenance documentation would outscore a community holding thorough written records and a phone. That inverts the programme's purpose, and it would do so invisibly, because both inputs are legitimate and the resulting number would look reasonable.

The two questions are genuinely different:

asks
confidenceScorehow much is known about where this object came from?
coveragecould the registers have known anything at all?
qualityScorehow good is the record we just made of the object itself?

Why a good scan is not good provenance

Direct corollary of ADR-009. A flawless capture of a looted object is a flawless capture of a looted object. The capture record looks exactly like proof of legitimate ownership — signature, hash, timestamp, chain of custody — and is not. That resemblance is the risk, so the disclaimer is a field the format requires rather than a caption a UI might forget.

What this genuinely adds, and to whom

For a structurally-uncovered object it is the only new information the system can generate. The Bura askos scores 58 with 0 of 9 registers able to name it, because Bura funerary sites were never inventoried and the registers hold reports of thefts from documented collections. Its own timeline records the trap: the 1970 UNESCO Convention "requires a pre-existing inventory — which Bura sites lack."

Capture is the only mechanism in the programme that creates record rather than querying it. It cannot help an object already looted from an unrecorded site. It can start the record, from today, for everything still in museum, community or private custody.

The reconstruction gap — where this contributes something new

C2PA solves capture-time provenance for 2D media well: a hard binding over the asset bytes, an X.509 signature, an RFC 3161 timestamp, and in Truepic's implementation a signature applied inside the secure enclave before the image leaves the sensor.

None of it survives photogrammetry. As of spec v2.1–v2.4 the normative format list is JPEG, PNG, GIF, TIFF, BMFF video and PDF. glTF/GLB, USDZ, E57 and PLY are absent. The photographs can be sealed to a very high standard and the mesh built from them inherits none of it — the chain breaks at exactly the step that produces the artefact anyone will actually look at.

Every crowd-sourced heritage capture effort we surveyed — Rekrei/Project Mosul, Backup Ukraine, the Million Image Database — used general-purpose photogrammetry apps and sealed the 3D output not at all. Rekrei accepts any photograph with no submission quality protocol whatsoever.

ReconstructionBinding is our answer: one signed structure binding the source image set, how many of those carried a verifiable seal, the pipeline and parameters, the output mesh hash, and a perceptual hash as a soft binding so a re-exported copy can be re-associated. It does not make reconstruction reproducible — photogrammetry is not bit-deterministic — and chainComplete says so rather than implying a guarantee the format cannot make.

Alternative rejected: extend @dpa/identity instead of adding a stage

@dpa/identity already computes perceptual hashes and forensic signals, so capture could have gone there. Rejected because identity answers "is this the same object as that one?" against material already in hand, whereas capture answers "how do we get this object into the system at all?". Folding them would have put the quality rubric behind the same module boundary as the forgery-risk score, and those two numbers must not meet either.

Cost

  • A new package, a new schema type, and a new route.
  • ADR-004 and ADR-007 are reopened.
  • Decision #5 in MEETING-BRIEF §5 — the source-community disclosure tier, decided with no source-community input — escalates from weak to blocking. It was tolerable while the system only described objects. It is not tolerable while the system holds photorealistic models of grave goods.
  • The band thresholds are ours and are not standards-backed. There is no ratified cross-body numerical rubric for heritage 3D capture; the London Charter, which is the most widely adopted framework, is a principles document. Every threshold sits in one file so the argument can be had against specific numbers.
Contents

The capture protocol — Stage 0

A protocol a museum, community archive or field team can follow to record a cultural object in 3D, such that the record is worth something afterwards.

Implemented in packages/capture. Demonstrated at /capture. Decision record: ADR-010. Origin: AABC feedback.


1. Why this exists

Every other stage in the DPA pipeline begins after an object has already been recorded by somebody. @dpa/evidence searches archives. @dpa/assess scores what was found. @dpa/govern notarises the result. Where no record was ever created, all of them can do is measure the hole.

That hole is not an edge case. It is the motivating population of the entire programme.

The Bura askos in this repository scores 58 with 0 of 9 registers able to name it. Not because the search was poor — because Bura funerary sites were never inventoried, and INTERPOL SWOA, the FBI National Stolen Art File and the Carabinieri TPC archive hold reports of thefts from documented collections. An object from an unrecorded site cannot appear in them. Ever. However much is later learned about it.

Its own timeline records the trap exactly:

Niger ratified the 1970 UNESCO Convention on 6 December 1997. Any Bura object removed after that date is in breach of Niger law and the Convention. However, the Convention requires a pre-existing inventory — which Bura sites lack.

Capture is the only mechanism in this programme that creates record rather than querying it. It cannot help an object already looted from an unrecorded site. It can start the record, from today, for everything still in museum, community or private custody.


2. What a capture proves, and what it does not

A capture record carries a signature, a content hash, a timestamp and a chain of custody. That is exactly what proof of lawful ownership looks like. It is not that, and the resemblance is the single biggest risk in this stage.

AttestsAn object with these measurable characteristics was observed in this condition, at this place, at this time, by this party — and this is a tamper-evident record of that observation.
Does not attestThat the object was lawfully excavated, exported, acquired, or is lawfully held.

A flawless capture of a looted object is a flawless capture of a looted object.

This is ADR-009no register check returns "clear" — applied to a new surface. It is enforced structurally rather than editorially: attests and doesNotAttest are mandatory fields of the schema, and buildCaptureRecord throws if the legitimacy disclaimer is missing. A record without it cannot be constructed.


3. The three numbers

NumberAsksPackage
confidenceScoreHow much is known about where this object came from?@dpa/assess
coverageCould the registers have known anything at all?@dpa/assess
qualityScoreHow good is the record we just made of the object itself?@dpa/capture

These never mix. @dpa/capture may not import @dpa/assess, and @dpa/assess may not import @dpa/capture; a test asserts it at the module boundary.

If capture quality contributed to provenance confidence, a museum could raise an object's provenance confidence by buying a better camera. An institution with a well-funded imaging department and no provenance documentation would outscore a community holding thorough written records and a phone. That inverts the purpose of the programme, and it would do so invisibly, because both inputs are legitimate and the resulting number would look perfectly reasonable.


4. Fitness classes

Quality is expressed as fitness for a purpose, not as a grade, because "7.2 out of 10" tells a registrar nothing about whether they may rely on it.

ClassFit for
ReferenceServing as the identifying record of the object. Measurable, colour-accurate, complete enough that a future capture can be compared against it to detect change or substitution.
StudyScholarly comparison and condition assessment. Not fit to be the sole identifying record — a determined substitution could survive comparison against it.
IndicativeDisplay. Shows what the object looks like; proves nothing about it. Not fit to measure, compare or identify against.
InsufficientNothing. Recording it as an object record would misrepresent what is known.

The class is the worst dimension, not the average

Four hundred photographs that are all out of focus produce an out-of-focus mesh. A capture with flawless angular coverage, flawless overlap, flawless lighting and no scale bar is a capture of an object whose size is unknown, and no amount of the other dimensions buys that back.

So the class is the minimum across the ten dimensions. This is the opposite of the provenance scorer (ADR-002), which accumulates — because evidence genuinely does add up and surface detail genuinely does not.

There is therefore always exactly one thing worth fixing next. Averaging would hide it, which is the specific failure of every five-star rating anyone has ever been shown.

Why there is also a score

qualityScore (0–100) is the mean of the normalised dimensions. It is a progress meter, not a fitness rating: it exists so a contributor re-shooting a session can see movement before the class flips over.

The two are allowed to disagree, deliberately. A capture can score **86/100 and still be Indicative** if the scale bar is missing. A test locks that case in, so that nobody later "simplifies" the pair into a single misleading number.


5. The ten dimensions

Nine can be measured while capturing. The tenth cannot be known until the photographs are processed.

#DimensionMeasuresLive?
1Angular coverageFraction of the viewing sphere the camera visited
2Surface completenessFraction of the surface actually observed
3Image overlapMean overlap between adjacent frames
4SharpnessVariance of the Laplacian
5ExposureFraction of pixels clipped at black or white
6Ground sample distanceMillimetres of object per pixel
7Scale referenceCalibrated scale bar / AR-derived / absent
8Colour referenceColour target present
9Lighting consistencyStability of lighting across frames
10Device metadataFraction of frames retaining focal length, sensor and pose

Two that matter more than they look

Scale reference (7). Without a calibrated scale in shot, the model has shape but no size, and an object with no size cannot be measured, compared against a written description, or matched to an excavation record. Phone AR tracking gives approximate metric scale; for a reference record a physical scale bar remains the standard. This is why a capture can be excellent on nine dimensions and still not be a reference record.

Colour reference (8). Condition assessment depends on colour being comparable across captures years apart. Without a colour target, the record carries whatever cast the room lighting had, and a later capture cannot distinguish genuine deterioration from a different set of light bulbs.

Where the thresholds come from, honestly

The individual measures are standard practice: variance of the Laplacian and Tenengrad as focus measures, histogram clipping fractions for exposure, 60–80% image overlap for close-range heritage photogrammetry (CIPA), ground sample distance in mm/pixel, and angular sector coverage — which is what Apple's capture dial discretises.

The bands — where reference ends and study begins — are ours.

There is no ratified cross-body numerical rubric for heritage 3D capture. The London Charter (2009) is the most widely adopted normative framework and is a principles document with no numeric tiers. Historic England, the Smithsonian DPO and Europeana have each proposed their own; none is a cross-body standard, and several were not publicly retrievable when we checked.

Our bands are calibrated to be achievable on a mid-range phone by a museum registrar with no photogrammetry training, because that is the population this protocol is for. Every threshold sits in one file (rubric.ts) so the argument can be had against specific numbers rather than against a vibe.


6. Real-time guidance

"In real time we should [have] ways to help them understand adjustments they need to make." — AABC

One instruction at a time. Somebody walking backwards around a plinth holding a phone can act on one instruction. Presenting ten dimensions and inviting them to optimise produces a contributor who ignores all ten. Apple's ObjectCaptureSession — the only mobile capture API with publicly documented real-time quality signals — shows a single corrective message at a time against a segmented dial, and that is not a UI preference.

Only live dimensions can drive it. Reprojection error, mesh watertightness and point-cloud density are outputs of reconstruction. Guidance built on them is a post-mortem, and a contributor who has packed up and gone home cannot act on it. Dimension 10 is reported as deferred, explicitly, so nobody is told "all good" by a system that has not finished looking.

Stability matters more than precision. Two dimensions hovering near the same threshold would otherwise swap the headline instruction several times a second, which reads as malfunction and cannot be obeyed. The guidance function takes the previous instruction and keeps it unless something is clearly worse.

Nothing unmeasured counts as passing. A dimension the app never checked is treated as absent, not as satisfied — the same discipline as ADR-009. Silence is not a clear result.


7. Where the provenance chain breaks — and what we do about it

C2PA solves capture-time provenance for photographs well: a hard binding over the asset bytes, an X.509 signature, an RFC 3161 timestamp, and in Truepic's implementation a signature applied inside the secure enclave before the image leaves the sensor.

None of it survives photogrammetry.

As of spec v2.1–v2.4 the normative format list is JPEG, PNG, GIF, TIFF, BMFF video and PDF. glTF/GLB, USDZ, E57 and PLY are absent. The photographs can be sealed to a very high standard and the mesh built from them inherits none of it. The chain breaks at exactly the step that produces the artefact anyone will actually look at.

Every crowd-sourced heritage capture effort we surveyed — Rekrei / Project Mosul, Backup Ukraine, the Million Image Database — used general-purpose photogrammetry apps and sealed the 3D output not at all. Rekrei accepts any photograph with no submission quality protocol whatsoever.

ReconstructionBinding

One signed structure binding:

  • the source image set, by a hash over the sorted per-image hashes;
  • how many of those carried a verifiable capture-time seal;
  • the pipeline and its parameters;
  • the output mesh hash (hard binding);
  • a perceptual hash of the mesh (soft binding), so a re-exported or metadata-stripped copy can still be re-associated with this record.

chainComplete is true only when every source image was sealed and the pipeline is recorded. A chain that is 90% sealed is not 90% of a proof: the unsealed 10% is precisely where a substituted photograph would be inserted, and an attacker chooses where to attack.

It does not make the reconstruction reproducible. Photogrammetry pipelines are not bit-deterministic, and chainNote says so rather than implying a guarantee the format cannot make.


Capture assets never default to public.

A mesh is not a metadata field. It carries two risks no text field carries:

  1. It is a replication asset. A scan good enough to study is good enough to 3D-print or to forge from.
  2. For funerary or sacred material, public visibility may itself be the harm.

The Bura askos — the case this whole protocol is aimed at — is a grave good, and communities in the Tillabéri region regard the burial sites as ancestral ground. Publishing a rotatable model of grave goods is a consent question, not a configuration setting.

MaterialDefault tier
Ordinarymuseum
Funerarysource-community
Sacredsource-community

That default may be wrong. It is wrong in the direction that can be corrected, whereas publishing first cannot be undone.

This is a stopgap, not an answer. The source-community disclosure tier was designed with no source-community input, which was tolerable while the system only described objects and is not tolerable now that it will hold photorealistic models of funerary material. It is tracked as blocking in BACKLOG P0 §6.


9. Who captures

Capture operators are a separate enum from issuer classes, deliberately.

RoleMeaning
custodian-institutionThe institution holding the object, capturing its own holdings
community-stewardA source community recording material it claims cultural authority over
accredited-surveyorA professional engaged for the purpose
contributorCrowd-sourced. Unverified relationship to the object

Verification is tracked separately from the claimed role, because a claimed role is not a verified one, and the point of crowd-sourced ingestion is that most contributions arrive self-asserted and must still be worth accepting: self-asserted · email-domain-verified · institution-attested · on-site-witnessed.

Why not reuse IssuerClass? ADR-004 defines two issuer classes — pseudonymous-wallet and accredited-institution — and both make assertions about ownership. A volunteer photographing a museum vitrine has asserted nothing about title, and there must be no type by which their capture could be read as an ownership claim.

This is containment, not a solution. ADR-004 has no class for a contributor who records an object they do not own, and is reopened because of it.


10. What is not built

  • No metric extractors. The rubric is defined; nothing computes it from actual photographs yet.
  • No mobile client. /capture demonstrates the rubric and the guidance loop against fixtures; it does not touch a camera. Whether to build new, extend VANGO, or wrap Apple's Object Capture is open — and it reopens ADR-007, which placed VANGO outside the core on the premise that capture was out of scope.
  • No mesh perceptual hashing. outputPerceptualHash is specified as dHash over rendered orthographic views. Not implemented.
  • Not wired to a passport. A capture record is not yet attached to a Passport, notarised, or surfaced in /demo, /coverage or /disclosure. The end-to-end story — museum scans, scan is provenance-verified, artefact appears in the 3D exhibit — is not connected. /exhibit still renders procedural geometry.
  • One citation unverified. The feedback cited a *"Manhattan Bridge pedestrian capture protocol."* No protocol of that name exists in any public source we could reach — not the Library of Congress HAER record for the bridge, not Starling Lab, not C2PA, not NYC DOT. The closest real thing is Starling Lab and Numbers Protocol's Starlingcapture, which anchors a cryptographic birth certificate in device hardware at capture time. **It is not cited as prior art until the original source is identified.**

Full list: BACKLOG P0 §6.

Contents

v0.4 target architecture

The design that results from applying the decisions to the inventory.

Nothing here is built. This is the target.


Principle

The five projects are not five answers to one question. They are the consecutive stages of one pipeline, built in parallel by teams who could not see each other's stage. v0.4 does not invent a new architecture — it draws the one that already exists.


The pipeline

                        ┌────────────────────────────┐
   photo / code /       │  1. IDENTIFY               │
   text description ───▶│  Gemini Vision  ·  SHA-256 │
                        │  dHash · keypoints · dedup │
                        └─────────────┬──────────────┘
                                      │ candidate object + fingerprints
                        ┌─────────────▼──────────────┐
                        │  2. INVESTIGATE            │
                        │  ONE evidence service      │
                        │  Tavily · Wikidata · Met · │
                        │  AIC · MoMA · Europeana    │
                        │  + tiered register checks  │
                        └─────────────┬──────────────┘
                                      │ sourced claims, each with tier
                        ┌─────────────▼──────────────┐
                        │  3. ASSESS                 │
                        │  accumulation scorer       │
                        │  ────────────────────────  │
                        │  confidenceScore  0–100    │
                        │  coverageClass    ◀── never folded in
                        │  forgeryRisk      ◀── separate number
                        └─────────────┬──────────────┘
                                      │
                        ┌─────────────▼──────────────┐
                        │  4. ISSUE                  │
                        │  one signed envelope       │
                        │  ┌──────────┬───────────┐  │
                        │  │ wallet   │ instit'l  │  │
                        │  │ did:pkh  │ P-256 CA  │  │
                        │  └──────────┴───────────┘  │
                        └─────────────┬──────────────┘
                                      │
                        ┌─────────────▼──────────────┐
                        │  5. GOVERN                 │
                        │  confidentiality envelope  │
                        │  + EAS notarisation        │
                        │    (contentHash only)      │
                        └─────────────┬──────────────┘
                                      │
                        ┌─────────────▼──────────────┐
                        │  6. MAINTAIN               │
                        │  StatusList2021 revocation │
                        │  amendment · claim filing  │
                        └─────────────┬──────────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              │                       │                       │
    ┌─────────▼────────┐   ┌──────────▼────────┐   ┌──────────▼────────┐
    │ 7a. ROLE VIEWS   │   │ 7b. 3D EXHIBIT    │   │ 7c. VANGO         │
    │ public / museum  │   │ artifact + POIs + │   │ visitor stamps    │
    │ enforcement /    │   │ provenance ribbon │   │ QR · EN/FR/IT     │
    │ source-community │   │                   │   │ (public tier only)│
    └──────────────────┘   └───────────────────┘   └───────────────────┘

Package layout

A monorepo. Each package has exactly one owner and one reason to change.

dpa/
├── packages/
│   ├── schema/          ← canonical passport envelope (ADR-001)
│   │                      Zod types + JSON Schema + fixtures
│   │                      Nothing else defines a passport shape.
│   │
│   ├── identity/        ← stage 1
│   │                      image fingerprinting (SHA-256, dHash, keypoints,
│   │                      ELA, optional MobileNet), duplicate detection,
│   │                      Gemini Vision identification
│   │                      FROM digital-passport-artworks + provenance-search
│   │
│   ├── evidence/        ← stage 2 (ADR-008)
│   │                      one connector interface, all sources behind it
│   │                      registers/  — tiered, with signed caveats (ADR-009)
│   │                      FROM arts-provenance-agent/src/tools + provenance-search
│   │
│   ├── assess/          ← stage 3 (ADR-002, ADR-003)
│   │                      scorer.ts    — accumulation, THE only scorer
│   │                      coverage.ts  — mandatory, never folded into score
│   │                      forgery.ts   — separate number, never merged
│   │                      FROM arts-provenance-agent/src/agent/assessRisk.ts
│   │                           + arts-provenance-agent/src/lib/coverage.ts
│   │
│   ├── issue/           ← stage 4 (ADR-004)
│   │                      wallet.ts       — secp256k1 / EIP-191 / did:pkh
│   │                      institution.ts  — ECDSA P-256 CA chain
│   │                      canonicalise.ts — the hash both sign over
│   │                      FROM arts-provenance-agent + digital-passport-artworks
│   │
│   ├── govern/          ← stage 5 (ADR-005, ADR-006)
│   │                      envelope.ts   — field→tier map, canSee, redactForRole
│   │                      notarise.ts   — EAS on Base Sepolia
│   │                      FROM dpa-prototype  ← the framework's core contribution
│   │
│   ├── lifecycle/       ← stage 6
│   │                      StatusList2021 revocation, amendment, claim filing,
│   │                      human review queue
│   │                      FROM digital-passport-artworks
│   │
│   └── payments/        ← cross-cutting
│                          x402 / USDC on Base Sepolia for gated registers
│                          FROM arts-provenance-agent
│
├── apps/
│   ├── api/             ← the only thing that touches packages/govern.
│   │                      EVERY response is role-parameterised. Redaction
│   │                      happens here, never in a client. (ADR-005)
│   │
│   ├── web/             ← role-gated views + field capture (camera)
│   │                      FROM dpa-prototype + provenance-search
│   │
│   ├── agent/           ← the CLI research agent, retained
│   │                      FROM arts-provenance-agent
│   │
│   └── exhibit/         ← 3D artifact viewer, IRenderer abstraction
│                          FROM dpa-prototype
│
└── contracts/           ← DPARegistry.sol + EAS schema registration
                           FROM dpa-prototype

  ── separate repo ──
  VANGO                 ← consumes apps/api public tier only (ADR-007)

The three numbers, and why they are three

A recurring failure across the existing repos is one number carrying several meanings. v0.4 names three numbers and forbids combining them.

NumberQuestion it answersRangeNever
confidenceScoreHow much sourced provenance evidence exists?0–100, accumulated from 30…adjusted by coverage
coverageClassCould that evidence have existed?well-covered / partially-covered / structurally-uncovered…reduced to a number
forgeryRiskIs this image what it claims to be?0–100…merged with confidenceScore

The rendering rule: a confidenceScore may never be displayed without its coverageClass adjacent to it. Sorting or ranking objects by score across coverage classes is incorrect and should fail review.

Worked example, already in the codebase:

ObjectScoreCoverageReading
Rosetta Stone34/100structurally uncoveredThe registers could never have named this. The score reflects our blindness, not the object.
Getty Bronze26/100well coveredRecords ought to exist here and they are thin. The score is about the object.

The lower number is the more informative one. Without coverage they are indistinguishable and both readings are wrong.


Disclosure model

Redaction is enforced in apps/api, driven by the signed fieldTiers map in the passport. No client is trusted to redact.

              public   source-community   museum   enforcement   owner
title            ●            ●             ●           ●          ●
period           ●            ●             ●           ●          ●
image            ●            ●             ●           ●          ●
confidenceScore  ●            ●             ●           ●          ●
coverageClass    ●            ●             ●           ●          ●
notarisation     ●            ●             ●           ●          ●
─────────────────────────────────────────────────────────────────────
claimStatus      ○            ●             ●           ●          ●
custodianship    ○            ●             ●           ●          ●
sourceCommunity
  statement      ○            ●             ●           ●          ●
─────────────────────────────────────────────────────────────────────
dimensions       ○            ○             ●           ●          ●
condition        ○            ○             ●           ●          ●
loanEligibility  ○            ○             ●           ●          ●
─────────────────────────────────────────────────────────────────────
fullProvenance   ○            ○             ○           ●          ●
registerHits     ○            ○             ○           ●          ●
holderPseudonym  ○            ○             ○           ●          ●
contactEscrow    ○            ○             ○           ●          ●
─────────────────────────────────────────────────────────────────────
holderIdentity   ○            ○             ○           ○          ●

source-community is orthogonal, not a rung on the ladder: it sees the public tier plus the source-community tier, and never sees museum or enforcement internals.

This table encodes a substantive claim about what source communities should and should not see, and no source community has been consulted about it. It is the single most important thing in this plan to validate with real stakeholders. See ADR-005.


Data flow for one registration

1.  Holder submits image + metadata (web or field capture)
2.  identity/  → sha256, dHash, keypoints; dedup against registry
                 ├── exact/near duplicate → block, return existing passport id
                 └── inconclusive        → human review queue (lifecycle/)
3.  identity/  → Gemini Vision proposes candidate object; holder confirms
4.  evidence/  → grounded search across permitted sources
                 → tiered register checks, each recording access/verdict/caveat
                 → x402 payment if a gated register is selected (payments/)
5.  assess/    → confidenceScore (accumulation)
                 coverageClass  (computed independently, never folded in)
                 forgeryRisk    (from identity/ signals)
6.  issue/     → canonicalise → contentHash → sign
                 issuerClass = wallet | institution
7.  govern/    → attach signed fieldTiers map
                 → EAS attest contentHash on Base Sepolia
8.  lifecycle/ → allocate StatusList2021 index (revocable from birth)
9.  api/       → serves the passport, redacted per requesting role

What is deliberately still out of scope in v0.4

Carried forward unchanged from the framework prototype's non-goals:

  • Real PII handling and a GDPR audit
  • Real KYC, real custody, real money (x402 remains testnet USDC)
  • Physical tagging — RFID / NFC / microdot (stub only)
  • Fractional ownership, lending, insurance integration
  • Mainnet, or any chain other than Base Sepolia
  • The full restitution workflow

Added to the out-of-scope list for v0.4 specifically:

  • Merging VANGO (ADR-007)
  • Any new evidence source beyond consolidating the existing ones — the source-bias fix is P0 in the backlog but it is a v0.5 body of work, because it requires validation against known cases, not just new connectors
  • Real haptics or voice in the exhibit — HopeOS does not provide them, and the IRenderer abstraction is where that would land later
Contents

Inventory — all work to date

An assessment of the five working systems in the DPA programme as of the v0.4 consolidation review. Each entry records what the project is, what it uniquely contributes, where it conflicts with the others, and what v0.4 does with it.

Assessments are of fit for consolidation, not of quality. Every project here works, and several are more polished than the framework prototype they derive from.


1. yorkerhodes3/dpa-prototype — the framework reference implementation

What it is. A vertical slice of Annex A: a pseudonymous owner registers one artwork, the system hashes it, writes an attestation to Base Sepolia via the Ethereum Attestation Service, and renders the passport through role-gated views. Ships with a 3D exhibit module.

StackNext.js 14 App Router · TypeScript strict · three.js · Solidity 0.8.24 + Foundry · pnpm
ChainBase Sepolia (chainId 84532) · EAS 0x4200…0021 · SchemaRegistry 0x4200…0020
VisibilityPrivate
State12/12 milestones complete · build + typecheck clean · 8/8 routes 200 · 4/4 contract tests pass

Unique contributions — present in no other repo.

  1. The confidentiality envelope. schemas/confidentiality-envelope.json plus lib/dpa.ts (canSee(), redactForRole()) — a declarative field→role visibility map. This is the framework's central governance mechanism and it exists only here.
  2. The role model. public < museum < enforcement < owner is linear, but source-community is orthogonal: it sees public and source-community fields only, never museum or enforcement internals. claimStatus and custodianship are promoted into the source-community tier so a community can see claims made on its own objects. *This was an implementation decision made under time pressure and has never been validated with an actual source community — see BACKLOG.*
  3. On-chain notarisation. A real EAS integration with a verified schema (bytes32 contentHash, string passportId, uint8 confidentialityLevel) — deliberately minimal, no PII on-chain — plus a DPARegistry.sol contract with register / flagClaim / attestCustodianship.
  4. The 3D exhibit. An IRenderer abstraction with a HopeOS adapter, being the only realisation anywhere in the programme of the "shadow to light" thesis at the public-visibility end.

Weaknesses.

  • No real evidence. Provenance research is a mocked INTERPOL/ALR lookup returning no match | possible match | claimed. Every derivative repo is dramatically stronger here.
  • The 3D artifact is a placeholder — Khronos DamagedHelmet.glb, a sci-fi helmet, not a cultural object.
  • HopeOS turned out to be a single HTML file (MediaPipe Hands + a Three.js particle globe), not the haptic/voice SDK the plan assumed. The adapter isolates this, but the exhibit's haptic and voice ambitions are unbacked.
  • Private, so the students building on it could not see it.

v0.4 disposition — PROMOTE. The confidentiality envelope and the notarisation spine become the governance layer of the whole system (ADR-005, ADR-006). The mocked lookup is deleted and replaced by the real evidence service (ADR-008). The exhibit becomes an optional presentation client.


2. Ethical-Tech-CoLab/arts-provenance-agent — the most mature work

What it is. An "x402-native" research agent: it takes an object, grounds claims against a permitted source list via Tavily, runs tiered stolen-art register checks, optionally pays for premium checks with USDC micropayments over Coinbase x402 on Base Sepolia, and emits a signed JSON-LD Verifiable Credential.

StackTypeScript (~28 modules) · Zod schemas · Tavily · Wikidata SPARQL · Coinbase x402
Identitydid:pkh · secp256k1 · EIP-191 personal_sign over a keccak256 content hash
VisibilityPublic
ArtefactsDPP-Paper.md · PEER-REVIEW.md · BACKLOG.md · SECURITY.md · fixtures/ · schema/

This is the strongest work in the programme, and the only part of it that has been through independent academic review (verdict: minor revisions). That peer review is a credibility asset AABC will care about and should be surfaced in the meeting, including the findings it has not yet closed.

Unique contributions.

  1. The coverage model (src/lib/coverage.ts). Records, per register, what kinds of loss it can hold, for which regions, and whether it can identify an individual object at all — then reports how many registers could have named this object. It never adjusts the score, deliberately: folding coverage in would recreate the one-number-two-meanings defect it exists to fix. The score is declared comparable only within a coverage class. See ADR-003.
  2. **"No register check can return clear."** The strongest available negative is no-evidence-found. Encoded in the schema's verdict enum, not merely in prose. See ADR-009.
  3. Tiered register access, recorded in the signed credential. Only Wikidata is genuinely machine-queryable; INTERPOL, FBI NSAF, Carabinieri TPC, the German Lost Art Foundation and the Getty Provenance Index are domain-scoped search or human referral only. The schema carries access, verdict, method, caveat and officialSearch together, signed, so a verdict cannot be stripped from the caveat that qualifies it.
  4. Per-claim evidence provenance. Every TimelineEvent requires a source URL, a sourceType, a verifiedBy authority and a VerificationTier (verifiedByAuthority / reportedInPress / inferred). No source, no claim.
  5. Payment rails. x402 micropayments for gated commercial registers — the only answer anywhere in the programme to "who pays for a lookup."
  6. Intellectual honesty about its own bias. AUTHORITATIVE_DOMAINS is five Western institutions plus one commercial register; the repo states plainly that this searches worst exactly where the motivating harm lives.

Known-open weaknesses (self-declared, from its own BACKLOG.md and PEER-REVIEW.md).

  • Two disagreeing scorers inside this one repo. The CLI agent (src/agent/assessRisk.ts) accumulates from a base of 30; the web pipeline (src/web/pipeline.ts) deducts from 100. They disagree on the same object. The peer review designated the accumulation model canonical; the deduction model still ships.
  • Overclaim retracted mid-document. §04/§01 claim "hallucinated history structurally impossible"; §13 concedes a false claim wearing a real source URL is not prevented. The correct claim is that unsourced history is structurally impossible.
  • Nothing validated against ground truth. It is unclear whether showcased scores were computed or hand-authored.
  • Case-count inconsistency across the paper (five vs. fifteen vs. a sixth object).
  • Not integrated with any UI other than its own; no disclosure control at all.

v0.4 disposition — PROMOTE TO CORE. Its passport schema becomes the canonical envelope (ADR-001), its accumulation scorer becomes the single scorer (ADR-002), its coverage module becomes mandatory (ADR-003), and its tool layer absorbs provenance-search's connectors (ADR-008). Its open peer-review findings carry over into the v0.4 backlog rather than being closed by the merge.


3. Ethical-Tech-CoLab/provenance-search — evidence breadth and field capture

What it is. "Arts & Artifacts — Provenance Intelligence." An Express app offering three ways in — text form, image upload, and live camera capture — that identifies an object with Gemini Vision and then verifies it across eight sources.

StackHTML/JS · Express (server.js) · Gemini · PWA manifest + service worker
Deployedhttps://provenance-search-production.up.railway.app
SourcesTavily · The Met · Art Institute of Chicago · MoMA · Wikipedia · Wikidata SPARQL · Europeana · Gemini
VisibilityPublic

Unique contributions.

  1. Field Mode. Camera capture on a phone, in a museum, in front of the object. This is the only realistic acquisition path for the actual users the framework describes, and no other repo has it.
  2. Image → object identification via Gemini Vision (/api/identify/api/verify). The agent assumes you already know what the object is.
  3. Breadth of free sources, including a bundled 3.7 MB gzipped MoMA static export used because the live API is Cloudflare-blocked — a pragmatic solution worth keeping.
  4. The "general knowledge fallback." For famous works it will answer from model knowledge but labels the result isGeneralKnowledge: true, never marks it verified, and auto-attaches a medium-severity flag. This is a good pattern and should be generalised.
  5. Its own research paper and backlog, with the same hostile-but-fair self-review discipline as the agent.

Weaknesses.

  • A third scoring algorithm. computeConfidenceScore in server.js starts at 100 and deducts 30 per custody gap, 25 if fewer than three of seven sources verified, 10 per high-severity flag, 10 for anomalous valuation. It conflicts with both of the agent's models.
  • Its own backlog concedes the arithmetic in the paper is wrong in the one place the reader is invited to check it (3 × 30 leaves 10, not 0).
  • No signing, no passport artefact, no disclosure control, no persistence.
  • Duplicates the agent's Tavily and Wikidata integration with a different allow-list.

v0.4 disposition — ABSORB, then RETIRE the shell. The connectors, the Gemini Vision identification step, and the field-capture UX move into v0.4. The computeConfidenceScore function is deleted, not ported (ADR-002). The Railway deployment stays live as a demo until v0.4 has a working equivalent.


4. Ethical-Tech-CoLab/digital-passport-artworks — lifecycle and forensics

What it is. A single-file, fully client-side walk through the entire passport lifecycle: upload → fingerprint → similarity search → forensic signal fusion → risk scoring and routing → certificate chain → issuance → revocation → verification. Originally by @ChristineLumen.

StackVanilla HTML/CSS/JS, single file, no build · Web Crypto API · Canvas API · optional TensorFlow.js MobileNet
Livehttps://ethical-tech-colab.github.io/digital-passport-artworks/
VisibilityPublic

Unique contributions.

  1. The only complete lifecycle. Every other repo issues; this one issues, revokes, reinstates, and verifies. Revocation uses a StatusList2021-style signed bitstring. No other repo has any answer to "this passport was wrong."
  2. Institutional PKI. A real three-tier ECDSA P-256 chain — governance root → federated root → issuing CA — generated in-browser. This models accreditation, which the wallet-based model in the agent does not. See ADR-004.
  3. Image forensics and duplicate detection. Three independent fingerprints (SHA-256, perceptual dHash, angle count) plus multi-scale keypoint matching (Harris corners, SIFT-style descriptors, RANSAC), optional MobileNet embeddings, block-relative ELA, noise-floor and spectral/edge energy checks. This directly serves a framework requirement — *"has this object already been registered under a different identity?"* — that nothing else addresses.
  4. A human review gate. Inconclusive risk or unconfirmed similarity routes to a human instead of auto-issuing. The only repo that models the human-in-the-loop the framework requires.
  5. Tamper demonstration. You can edit the passport JSON in the browser and watch verification fail — an unusually effective demo of why signing matters.

Weaknesses.

  • Session-only. No persistence; refresh clears everything. Duplicate detection therefore proves the pipeline, not a registry — which the README is honest about.
  • Its crypto is P-256 / Web Crypto, incompatible with the agent's secp256k1 / EIP-191 wallet signatures.
  • Its risk score is a fourth scoring surface (forgery risk, not provenance confidence — the two are legitimately different, but this is not stated anywhere and readers will conflate them).
  • No provenance research at all — it assesses the image, not the history.

v0.4 disposition — ABSORB AS TWO MODULES. (a) The issuance/revocation lifecycle and the institutional CA chain become the second issuer class (ADR-004) and the revocation service. (b) The image fingerprinting and forensic fusion become the identity/dedup stage of the pipeline. Note explicitly that forgery risk and provenance confidence are two different numbers and must never be merged.


5. Ethical-Tech-CoLab/VANGO — the visitor layer

What it is. A mobile-style art passport for collecting stamps at art experiences. Scan a QR code or type a code, earn a stamp with a custom vintage illustration, artist, venue and date. Bio page, settings, dark/light theme, and full UI translation across English, French and Italian.

StackReact · Vite · lucide-react · localStorage
Livehttps://ethical-tech-colab.github.io/VANGO/
VisibilityPublic
NotableA realistic 3D page-turn book UI; all authentication was deliberately removed

Unique contributions.

  1. The only consumer-facing surface in the programme. Everything else is built for researchers, museums or enforcement. VANGO is built for a visitor.
  2. Multilingual from the start (EN/FR/IT) — the only repo that treats internationalisation as a requirement rather than a later concern, which matters for a programme whose stakeholders are Italian, French and Anglophone.
  3. A physical→digital bridge via QR codes on installation signage.
  4. Its catalogue already includes real programme context — BURA01 ("Bura Ceramics", Niger, AABC) sits alongside the fictional entries, which is exactly the kind of object the framework is about.

Weaknesses relative to the core.

  • It is a stamp collection app, not a provenance instrument. Its "passport" and the DPA "passport" are different objects that share a name — a genuine source of confusion in any joint demo.
  • localStorage only; stamps are unverifiable and trivially forged. That is fine for its purpose and unacceptable for the core.
  • Authentication was removed, so there is no notion of who a holder is.
  • Its catalogue is a hardcoded CATALOG object in src/App.jsx.

v0.4 disposition — KEEP AS A CLIENT, DO NOT FOLD IN. See ADR-007 for the full argument. Summary: its concern (visitor engagement) is orthogonal to provenance assurance, and merging it would put an unauthenticated localStorage app inside the trust boundary. Instead it becomes the reference consumer of the public disclosure tier — the demonstration that a role-gated passport can safely drive a public-facing product. This is a promotion, not a demotion: it makes VANGO the visible end of "from shadow to light."

Two changes are asked of it: resolve the passport-name collision, and read its catalogue from the v0.4 public API instead of a hardcoded object.


Cross-cutting conflict summary

ConflictRepos involvedSeverityResolution
Three provenance confidence scores that disagreeagent (×2), provenance-searchCritical — undermines the auditability thesisADR-002
Two cryptographic trust modelsagent (secp256k1/did:pkh), digital-passport-artworks (P-256 CA chain)HighADR-004 — keep both, as issuer classes
Four passport data shapesall fourHighADR-001
Duplicate evidence retrievalagent, provenance-searchMediumADR-008
Disclosure control exists in exactly one repodpa-prototype onlyCritical — it is the framework's core ideaADR-005
Coverage epistemics exist in exactly one repoagent onlyCritical — without it scores mislead on the motivating populationADR-003
No revocation anywhere but one repodigital-passport-artworks onlyMediumabsorbed
"Passport" means two different thingsVANGO vs. everything elseLow but demo-breakingADR-007
Forgery risk vs. provenance confidence conflateddigital-passport-artworksMediumTwo named scores, never merged
Western source biasagent, provenance-searchHigh, and self-declaredCarried into BACKLOG as a first-class item

Maturity ranking, for planning purposes

  1. arts-provenance-agent — peer-reviewed, typed, schema-driven, honest about its limits. The natural core.
  2. provenance-search — deployed, broad, real users possible today.
  3. digital-passport-artworks — complete lifecycle, real crypto, but session-scoped.
  4. VANGO — polished and finished for what it is; smallest scope.
  5. dpa-prototype — architecturally the most important and functionally the least complete. It defines the shape of the problem; the others solved parts of it better.

That last line is the honest summary of the programme's position, and it is a good position: the framework was right about the structure, and the derivative work has outrun it on substance. v0.4 is the merge that lets both count.

Contents

Migration plan — per repository

What each repo's owner is being asked to do, and what they are explicitly not being asked to do.

Nothing is switched off. Every existing repo keeps working and keeps its deployment for the whole of v0.4. Consolidation is additive until the end-to-end demo works, then subtractive.


Ground rules

  1. No repo is deleted. Archived at most, and only after v0.4 demonstrably replaces it.
  2. No repo loses attribution. Every package in the v0.4 monorepo records the repo and author it came from.
  3. Research papers stay with their repos. DPP-Paper.md, Provenance-Search-Paper.md and VANGO-Paper.md are published outputs, not implementation detail.
  4. Deployments stay live until v0.4 has a working equivalent.

yorkerhodes3/dpa-prototype

Role in v0.4: the governance layer.

Moves toWhat
packages/governschemas/confidentiality-envelope.json, lib/dpa.ts (canSee, redactForRole), the role model
packages/govern/notarise.tslib/eas.ts — verified Base Sepolia addresses, attestation with mock fallback
contracts/DPARegistry.sol + its 4 Foundry tests, unchanged
apps/exhibitexhibit/src/renderer.ts, hopeos-adapter.ts
packages/schemaschemas/dpa.schema.json — as an adapter, not the canonical shape

Deleted: the mocked INTERPOL/ALR lookup. Replaced entirely by packages/evidence (ADR-008).

Asks of the owner:

  • Decide whether the repo moves into the CoLab org. It is private, which is the direct cause of its disclosure model not propagating to the work built on it. This is meeting decision #3.
  • Confirm the orthogonal source-community role was a provisional call, and support getting it reviewed by an actual source community (ADR-005).
  • Source a real cultural-object scan to replace DamagedHelmet.glb.

arts-provenance-agent

Role in v0.4: the core. More of this repo survives than any other.

Moves toWhat
packages/schemaschema/passport.ts — becomes the canonical envelope (ADR-001)
packages/assess/scorer.tssrc/agent/assessRisk.ts — becomes the only scorer (ADR-002)
packages/assess/coverage.tssrc/lib/coverage.ts — becomes mandatory (ADR-003)
packages/evidencesrc/tools/ — tavily, wikidata, registries
packages/issue/wallet.tssrc/lib/signing.ts, src/wallet/
packages/paymentssrc/agent/payForCheck.ts, src/lib/spend.ts, src/vendor/server.ts
apps/agentsrc/agent/orchestrator.ts, parseIntent.ts, timeline.ts

Deleted: src/web/pipeline.ts's deduction scorer. It is not deprecated, not feature-flagged — deleted. Its existence alongside assessRisk.ts is the defect the peer reviewer identified.

Asks of the owner:

  • Close the ground-truth validation gap before the AABC meeting. Run the canonical scorer over the hand-researched catalogue objects and publish machine scores beside the hand-authored narratives, labelled as to which is which. This is the highest-priority open peer-review finding and the one most likely to be asked about.
  • Fix the "hallucinated history structurally impossible" overclaim → unsourced history.
  • Reconcile the case count (five vs. fifteen vs. the uncounted Getty Bronze).
  • Confirm the accumulation model is canonical, on the record.

Not asked: to change the epistemics. The coverage model, the register access tiering, and the "no clear verdict" rule are being adopted programme-wide verbatim. This repo won those arguments.


Role in v0.4: evidence breadth and field capture. The shell retires; the capabilities do not.

Moves toWhat
packages/evidenceMet, Art Institute of Chicago, MoMA static export, Wikipedia, Europeana connectors
packages/identityGemini Vision identification (/api/identify)
apps/webCamera capture / Field Mode UX, PWA manifest + service worker
packages/evidence (as policy)The isGeneralKnowledge: true labelling pattern, generalised to all unsourced assertions (ADR-008)

Deleted: computeConfidenceScore in server.js. Not ported.

Asks of the owner:

  • Accept that displayed scores will change when the canonical scorer replaces the deduction model, and rewrite the paper's score-bearing sections accordingly. The known arithmetic error becomes moot rather than fixed.
  • Keep the Railway deployment live until apps/web has field capture working.
  • Document the MoMA static export's snapshot date so it can be recorded in the passport.

Not asked: to give up Field Mode. Camera capture in front of the object is the only realistic acquisition path for the users the framework describes, and this repo is the only one that has it.


digital-passport-artworks

Role in v0.4: lifecycle and forensics. Two distinct modules.

Moves toWhat
packages/identitySHA-256 + dHash + angle count fingerprints; Harris corners, SIFT-style descriptors, RANSAC; optional MobileNet embeddings; ELA, noise-floor, spectral/edge energy
packages/issue/institution.tsThe ECDSA P-256 governance root → federated root → issuing CA chain (ADR-004)
packages/lifecycleStatusList2021 revocation bitstring, reinstate, verification chain-of-trust
packages/lifecycle/review.tsThe human review gate — inconclusive risk or unconfirmed similarity routes to a human instead of auto-issuing
packages/assess/forgery.tsThe forgery-risk score — kept as a separate number, never merged with provenance confidence

Asks of the owner:

  • The record you sign changes shape (ADR-001), so signatures cover different bytes. This is a re-issue, not a rename.
  • Duplicate detection needs to become persistent. Session-only is correct for the current demo and insufficient for a registry.
  • Confirm the naming: forgery risk and provenance confidence are two different numbers on the same 0–100 scale, and readers will conflate them unless both are labelled everywhere.

Not asked: to abandon P-256 for secp256k1. The institutional CA chain is being adopted as a first-class issuer class precisely because it models accreditation, which the wallet model cannot. Both survive.


VANGO

Role in v0.4: reference client of the public disclosure tier. Stays its own repository (ADR-007).

Nothing moves. No code is extracted, no code is absorbed.

Asks of the owner — three, all small:

  1. Resolve the naming collision. A VANGO passport belongs to a visitor and collects venues. A DPA passport belongs to an object and collects evidence. Same word, opposite subject. Any joint demonstration will confuse the audience unless one of them is renamed — "visitor passport" / "object passport" is sufficient.
  2. Read the catalogue from the API. Replace the hardcoded CATALOG object in src/App.jsx with a fetch from the v0.4 public read endpoint. Codes then work without a redeploy, and VANGO becomes a live consumer rather than a mirror.
  3. Deep-link stamped artworks that have a DPA passport to their public view.

Why this is a promotion. VANGO consuming the public tier and demonstrably leaking nothing above it is a live test of the confidentiality envelope (ADR-005) that no internal document can substitute for. It is also the visible end of "from shadow to light" — the point at which a passport-shielded object is encountered by an ordinary visitor. Its catalogue already contains BURA01 (Bura Ceramics, Niger, AABC), which is exactly the class of object the framework exists for.

Revisit if AABC prioritises visitor-facing deployment over research infrastructure, in which case VANGO becomes the primary surface and ADR-007 should be reopened.


Sequencing

Additive first, subtractive last.

StepActionNothing breaks because
1Create the monorepo; publish packages/schema with adapters from all four existing shapesAdapters mean existing repos keep their own formats
2Extract packages/assess; re-run all quoted scoresExisting repos still run their own scorers
3Extract packages/evidence; both retrieval stacks now import itBehaviour is verified equivalent before switching
4Build packages/govern + apps/apiNew surface; nothing depends on it yet
5Build packages/issue + packages/lifecycleNew surface
6End-to-end demo through all seven stages, all four role viewsThe v0.4 deliverable
7Only now — archive superseded shells, retire duplicated code, point deployments at apps/apiv0.4 has demonstrably replaced them

Step 7 does not begin until step 6 works.

Contents

Design system

This document exists because a day was lost to problems that should not have cost an hour: page margins that differed per route, two typefaces on one page with no rule saying which was which, and text wrapping at 720px inside a card 1,886px wide. None of it failed a build, a typecheck or a test. All of it was obvious to anyone who looked at two pages in sequence.

The fixes are in the code. This document is the part that stops them coming back, and it is written to be enforceable rather than admired. Every rule below names the test that fails when the rule is broken. If a rule here has no test, it is a preference and is labelled as one.

Read this before adding a page, a component, or a theme.


0. The one-paragraph version

Nothing in a page or a component may contain a colour, a font, a size, a margin or a width. Those live in packages/theme as tokens, and in apps/web/app/globals.css as the template that consumes them. A page supplies content and semantic class names. If you find yourself typing a number into a .tsx file, you have found either a missing token or a missing rule — add it in the layer below and use it from above.


1. Why the failures happened

Each was the same mistake in a different costume: a decision that should have been made once was instead made per page, silently, by whoever wrote it last.

SymptomActual cause
Eight routes, three different left marginsThe frame was an opt-in container class. /capture never opted in and ran edge to edge
Vertical spacing differed page to page40 inline marginTop literals. 18 and 24 were the desktop ends of fluid tokens, frozen — so the scale came apart on a phone
Two typefaces on one pageThe serif was assigned by container (.card h2), so the same heading changed face depending on where it sat
A 1,886px card holding 720px of textThe measure was right; the container had no width rule at all and took everything the frame had

The through-line: conventions drift, structures do not. Everything below is expressed as a structure.


2. Token hierarchy

Three layers. Each may use the layer below it. Nothing may skip a layer.

packages/theme/src/themes/*.ts     what a brand decides    (colour, face, scale)
        ↓
packages/theme/src/css.ts          the variables emitted   (--bg, --measure, --gap)
        ↓
apps/web/app/globals.css           the template            (frame, rhythm, roles)
        ↓
app/**/page.tsx, components/*.tsx  content and class names (no values at all)

Rules

  1. A page or component contains no raw value. No hex, no px, no rem, no font name. Enforced by layout.test.ts — "no page or component hardcodes a width for a text container", "sets no block spacing inline", "a face is only ever set to re-resolve a nested theme scope".
  1. globals.css contains no colour literal outside its print block. Enforced by packages/theme/src/theme.test.ts.
  1. A new token is added to css.ts, not to a page. The variable list is asserted, so adding one is a deliberate act with a test change attached.
  1. Derive rather than add. --reading is calc(var(--measure) + 2 * var(--pad-card)) — not a new number. A rebrand that changes either token keeps them in step for free.

The one exception, and why it is not a loophole

BrandPreview renders a different theme inside the current page and therefore restates font-family. font-family is inherited, so it was already resolved against the page's tokens at body; without restating it, a nested data-theme scope silently shows the wrong face. The test allows a face to be named only on an element that also carries data-theme, and only for the body face. Headings need no equivalent, because the role rule resolves var(--serif) at the heading, inside the scope.


3. Horizontal layout: the frame, the column, the breakout

Three concentric ideas. Learn them in this order.

3.1 The frame

main itself carries the page frame — width, gutters, top padding. It is an element selector, specificity (0,0,1), weaker than any class. Two consequences, both deliberate:

  • every existing class rule still wins, so nothing had to be rewritten;
  • a new page cannot forget it, because there is nothing to remember.

Do not add a wrapper div around a page's content. Do not add a second <main>. Enforced by "the frame is applied to main, not opted into per page".

3.2 The reading column

main and .section lay out on named grid tracks:

[full-start]  1fr  [content-start]  min(100%, --reading)  [content-end]  1fr  [full-end]

Everything lands in content by default. That column is exactly one measure of text plus a card's padding, so a box that holds prose is the width of the prose it holds.

The column is centred, not pinned left. Capping children inside a wide centred frame while pinning them left does not remove empty space, it moves it to the right — which was the original complaint. Consequence, and it is intentional:

Prose and full-width content do not share a left edge. They share a centre axis. The document has exactly two vertical edges — one for sentences, one for structure.

.section restates the same tracks so that a nested section hands its children the same column. Without this the landing page put a section heading on one edge and the card grid inside that same section on another. Enforced by "main and .section share one set of tracks", "a nested section hands its children the same column".

3.3 The breakout

Content that genuinely needs width spans full. That is what the extra space on a large display is for: tables, card grids, the 3D viewer. Sentences are not.

Breakout is granted two ways, and both resolve to the same declaration:

:is(main, .section) > :is(.section, .grid, .tbl-scroll, .exhibit-grid, …),  /* by class */
:is(main, .section) > :has(.grid, table, canvas, .kv, .timeline, …) {       /* by content */
  grid-column: full;
}

The :has() clause is the important one. A class list goes stale the moment somebody puts a table in a card and does not think to widen it. With :has() the container widens itself. Where :has() is unsupported the rule is skipped and the container stays one measure wide — narrow, never broken. Enforced by "breakout is granted by what a container holds, not only by class".

3.4 Grid tracks

.grid-2 / .grid-3 cap each track at min(100%, var(--reading)) and centre the row. With 1fr each track absorbed whatever the frame had spare, so the box-to-content mismatch grew with the display — worst on exactly the large screens the responsive work was meant to serve (1.41× at 2560, 1.86× at 3440).

min(100%, …) is load-bearing: var(--reading) alone is a fixed length, and a track will grow to it regardless of how narrow the container is. That overflowed four routes at 390px. Enforced by "grid tracks cap at the reading width instead of absorbing the display".

3.5 The safety net

.card:not(:has(table, .grid, .kv, …)) { width: 100%; max-width: var(--reading); margin-inline: auto; }

A prose-only card is capped wherever it lands, including inside a container that declares its own template. This should be a no-op — and it costs nothing if it is. It is there so the next bespoke grid cannot silently reproduce the defect.

width: 100% is load-bearing and not redundant: a grid item with auto inline margins loses its default stretch sizing and falls back to fit-content, which collapsed these cards to 47px — the width of their own padding.

3.6 The single documented exception

.hero is the one full-bleed composition on the site. Above 120rem it becomes a two-column arrangement of title and lead, which is its own geometry and cannot borrow the shared one. It begins on the full edge, so it aligns with the card grid beneath it.

Below 120rem it is an ordinary content-column child, because a single column of prose belongs on the same edge as every other paragraph. The breakout lives in the media query, not in the base rule — declaring it full-bleed at every width put the landing page's opening sentence on a different edge from the rest of the page at 1280px.

If you are about to add a second exception: don't. Add a class to the breakout list instead, and if the geometry is genuinely bespoke, say so in a comment that explains what the shared system could not express.


4. Vertical rhythm

Spacing between blocks is set once, by the template:

main > * + * { margin-top: var(--gap-lg); }
  • Headings are exempted and keep type-derived spacing: more above (a heading opens a section), less below (it binds to what it introduces). A flat rhythm flattens document structure, which is wrong on long-form routes.
  • A container that owns its own spacing (.page-head, .section) is exempted rather than double-spaced.
  • .stack is the primitive for a group of elements that need the standard gap inside a wrapper. Use it instead of margins on children.

Never write spacing inline. Not style={{ marginTop: 24 }}, not marginTop: "24px". The literals 18 and 24 are the large-viewport ends of --gap and --gap-lg, which are fluid — hardcoding them freezes the desktop value and the scale comes apart on a phone, at precisely the width where space is scarcest. Enforced by "the space between page blocks is defined once", and a per-file assertion that no page or component sets a margin of 12px or more inline.

The trap that was actually hit

:not(.grid) > .card + .card looked correct and was not: .cap-metrics and .cap-live are display: grid without carrying the .grid class, so the rule stacked a margin on top of the grid's own gap — a 27px step where every other pair sat at 15px. Do not select layout by class name when you mean to select it by behaviour. Use .stack, or :has().


5. Typeface: by role, never by container

:where(main, footer) :where(h1, h2, h3, h4) { font-family: var(--serif); }
RoleFaceUsed for
Editorial--serifAll headings; .quote; .cap-class
UI / body--sansBody copy, labels, buttons, navigation
Machine--monoHashes, identifiers, scores, code

Two faces on a page is a design decision. Which face you get depending on where you sit is a bug. The original rules were .card h2, .wrap > .card h2 and similar — so on /brand, an h2 inside a card rendered serif while an h2 at the top level of the same page rendered sans, and the landing page's h1 was the only h1 on the site that was not serif.

Enforced by "every heading in the document body takes the editorial face" and "no container re-assigns a typeface to a heading" — which walks every rule in globals.css and fails on any selector that scopes a face to a heading by its surroundings.


6. Colour

Two kinds of colour, and conflating them is a disclosure bug wearing a stylesheet.

  • Chrome — background, text, lines, accent. Free for a brand to change.
  • Semantic — the five role colours and three coverage colours. These *carry the disclosure model*. A rebrand that collapses enforcement and public into two similar blues looks fine to whoever shipped it and is wrong.

Semantic colours are therefore validated: WCAG contrast plus a minimum CIELAB separation (ΔE 22 for roles, against a just-noticeable difference of ~2.3). Every registered theme is checked, so a bad brand fails CI.

Keep the variables independent even when the values coincide. The original :root had --community, --accent and --warn all literally #d4a556; rebranding the accent would have silently dragged the source-community role colour with it. Same colours today, separate variables, two tests locking the independence.

Enforced by packages/theme/src/theme.test.ts.


7. Responsive

Breakpoints exist to serve the viewport, in both directions. Verified at 390 / 820 / 1280 / 1920 / 2560 / 3440 across three themes and eight routes.

  • --w ramps between FRAME_MIN 1280 and FRAME_MAX 3440. Large-format displays (double-wide monitors, Surface Hubs) get more content, not more emptiness.
  • Prose does not widen with the display. --measure is already 68–72ch, at the top of the readable 45–75ch range. Extra width goes to structure.
  • Navigation collapses below 62rem into a real disclosure panel. It was overflow-x: auto, which hid half the site on touch with no affordance.
  • Nothing may overflow horizontally at any tested width.

8. How to add a page

  1. Create app/<route>/page.tsx. Its root is a fragment, not a wrapper.
  2. Open with <header className="page-head"> containing an eyebrow p.label and one h1. Exactly one page head per route.
  3. Write content as direct children. They land in the reading column automatically.
  4. For something that needs width, use .grid, .tbl-scroll, or add .wide. Prefer letting :has() decide.
  5. Add no margins, no widths, no colours, no fonts.
  6. Add the route to ROUTES in apps/web/layout.test.ts.
  7. Run pnpm -r test.

9. How to add a theme

  1. Add packages/theme/src/themes/<id>.ts; inherit from slate and override only what differs.
  2. Register it in the theme index.
  3. Run pnpm -r test. Contrast and ΔE separation are checked for you; a brand that damages the disclosure model fails CI rather than shipping.
  4. Check /brand — it renders the contrast report live.

10. How to verify a visual change

Source review does not catch layout defects; every defect in this document was found by measuring rendered geometry and missed by reading code.

pnpm build:web            # then serve apps/web/out

Drive Chromium over the routes and assert on measurements: left edges, container width versus the width of the widest text inside it, computed font-family, scrollWidth versus innerWidth. Check at 390 and at 3440, not just at your own screen size. Then re-check against the deployed site — the Pages build has a /DPA basePath and has caught bugs localhost did not.


11. Tests that enforce this document

FileLocks
apps/web/layout.test.tsFrame on main; one page head per route; no inline spacing at or above the token scale; reading column tracks; --reading derived and registered; breakout by :has(); grid track caps; prose-card cap; typeface by role; no hardcoded widths
packages/theme/src/theme.test.tsThe emitted variable list; no colour literals; WCAG contrast; ΔE separation of role and coverage colours; independence of --accent from --community

layout.test.ts covers components/*.tsx as well as app/**. Checking only pages missed 31 of the 40 inline spacing literals, because components render most of what is actually on screen.


12. Known preferences, not rules

These have no test and are open to argument:

  • The landing hero opens taller than the seven content routes (--head-top × 1.4). It is a stated multiple with a test binding the two, so changing it to 1 makes every route open identically.
  • Two typefaces rather than one. The serif carries editorial voice and the sans carries interface; a single-face system is defensible and would be a smaller thing to maintain.
  • --measure at 68–72ch is at the wide end of comfortable. 60–66ch would be more conservative and would narrow every prose container in proportion.
Contents

Consolidated backlog — v0.4

Every known gap across the five repositories, merged and prioritised for the consolidation. Sources:

  • arts-provenance-agent/BACKLOG.md + PEER-REVIEW.md (22 Jul 2026, minor revisions)
  • provenance-search/BACKLOG.md
  • dpa-prototype/BACKLOG.md
  • Gaps identified during this consolidation review that no repo had recorded

Ordering follows the convention the student repos established: in the order they would mislead a reader.

[x] means the work exists in this repository and is exercised by a test, by CI, or by the fixture build. It does not mean the upstream student repositories have adopted it. Where an item is only partly done it stays unchecked and says what is missing, because a half-ticked box is how a backlog starts lying.


P0 — Would mislead AABC at the status meeting

1. Nothing has been validated against ground truth

Source: arts-provenance-agent peer review, major finding, open

It is unclear whether the showcased confidence scores (e.g. Madame X "93/100") were machine-computed or hand-authored. Until this is resolved, no score should be shown to AABC.

  • Run the canonical scorer over every catalogue object
  • Publish machine scores beside hand-authored narratives, labelled as to which is which
  • Record the divergence honestly where they differ

2. Three disagreeing confidence scores

Source: peer review (within-repo) + this review (across-repo)

  • Delete the deduction scorer in arts-provenance-agent/src/web/pipeline.ts
  • Delete computeConfidenceScore in provenance-search/server.js
  • Designate the accumulation model canonical on the record (ADR-002)
  • Re-run and republish every score either repo has quoted

3. Source list excludes the motivating population

Source: declared in both arts-provenance-agent and provenance-search

AUTHORITATIVE_DOMAINS is five Western institutions plus one commercial register. The system searches worst exactly where the motivating harm lives.

  • Add the Getty Provenance Index
  • Add the German Lost Art Foundation
  • Add INTERPOL (referral tier — not machine-queryable)
  • Add source-country heritage authorities — needs AABC's relationships
  • Validate against known cases before shipping. New connectors change what every run returns; unvalidated additions are worse than the current bias
  • Update §08 of both papers to match

Needs budget and time. This is a v0.5 body of work, but it must be named at the meeting as the first substantive extension.

4. The headline safeguard contradicts itself

Source: peer review, major finding, open

§04/§01 claim "hallucinated history structurally impossible"; §13 retracts exactly that — a false claim wearing a real source URL is not prevented.

  • Reword to "makes unsourced history structurally impossible"
  • Cross-reference the §13 limitation at first claim
  • Fix in both DPP-Paper.md and the mirrored website content

5. ~~AABC feedback is not recorded anywhere~~ — CLOSED

Source: this review. Closed by the AABC meeting feedback of this cycle.

The consolidation was framed as responding to AABC feedback that existed in no repository. That feedback has now been received and recorded.

  • Capture the feedback verbatim in MEETING-BRIEF §7
  • Re-test all nine ADRs against it
  • Reopen any decision it contradicts — ADR-004 (crowd-sourced contributors are neither issuer class) and ADR-007 (VANGO's status was settled on the premise that mobile capture was out of scope; that premise is gone) are now open

The feedback creates a new P0 workstream — see P0 §6 below.


6. Crowd-sourced capture — the protocol AABC asked to be prioritised

Source: AABC meeting feedback. See MEETING-BRIEF §7 and ADR-010.

Stage 0 now exists as @dpa/capture. What shipped this cycle:

  • ADR-010 — capture is Stage 0; scan quality is a third independent number and never touches confidenceScore
  • packages/schema/src/capture.ts — the contract, including ReconstructionBinding
  • packages/capture — ten-metric rubric, limiting-factor assessment, real-time guidance loop with hysteresis, capture record. 38 tests
  • Class is the minimum across metrics, not the average — 400 blurred photos make a blurred mesh. Deliberately the opposite of the provenance scorer
  • attests / doesNotAttest are mandatory schema fields; buildCaptureRecord throws without the legitimacy disclaimer
  • Assets never default to public — funerary and sacred default to source-community
  • /capture — the rubric and guidance loop, interactive, verified in-browser across three themes and six viewports
  • docs/CAPTURE-PROTOCOL.md — the document to hand to a museum

What is not done:

Verification and standards

  • Track down the "Manhattan Bridge pedestrian capture protocol." It does not exist in any public source we could reach: not HAER, not Starling Lab, not C2PA, not NYC DOT. The closest real thing is Starling Lab / Numbers Protocol Starlingcapture, which seals media in device hardware at capture time. Ask whoever raised it at the meeting. Until then it is not prior art and is not cited as such.
  • Validate the band thresholds against a real standards body. Historic England's 2017 photogrammetry guidance, the Smithsonian DPO 3D tiers and the Europeana 3D task force criteria were all unreachable (403/404) when we checked. The numbers in rubric.ts are ours and are a starting position.
  • Confirm whether C2PA v2.4+ has added glTF/GLB or USDZ support. Our finding that it has not is based on the spec versions we could read.

Measurement — the rubric is defined but nothing computes it

  • Implement the metric extractors. Variance of Laplacian and histogram clipping are straightforward; angular coverage, surface completeness and overlap need pose estimates from ARKit/ARCore or from the reconstruction.
  • Ground sample distance requires object distance and sensor geometry. On a phone this means reading camera intrinsics, which vary by device.
  • Scale-bar and colour-target detection. ChArUco/ArUco detection is standard; no detector is wired up.

The capture client itself

  • There is no mobile capture app. /capture demonstrates the rubric and the guidance loop against fixtures; it does not touch a camera.
  • Decide the client strategy — and this is where ADR-007 is reopened: VANGO already solves the phone-camera problem for this programme. Build new, extend VANGO, or wrap Apple ObjectCaptureSession (the only mobile API with documented real-time quality signals — though it does not expose its own sharpness metric, so ours must be computed independently).
  • ADR-004 is reopened: there is no issuer class for a contributor who scans an object they do not own. CaptureOperatorRole is a containment measure, not an answer.

Consent — blocking, not deferred

  • The source-community disclosure tier was decided with no source-community input. Capture escalates this from weak to blocking: the Bura askos is a grave good, and the system will hold rotatable models of funerary material. Defaults are set closed (source-community for funerary and sacred), which buys time and is not a substitute for asking.
  • Define who may raise a capture asset's tier, and on whose authority.

Pipeline integration

  • A capture record is not yet attached to a Passport, notarised, or surfaced in /demo, /coverage or /disclosure.
  • The end-to-end story AABC described — museum scans, scan is provenance-verified, artefact appears in the 3D exhibit — is not wired. /exhibit still renders procedural geometry.
  • Perceptual hashing of a mesh (outputPerceptualHash) is specified as dHash over rendered orthographic views. Not implemented.

P1 — Consolidation work (the v0.4 build)

Mostly shipped. This section was written before the consolidation was built and listed the whole v0.4 plan as open. It is now marked against what is actually in this repository: 12 packages, 3 apps, 342 passing tests. [x] means the code exists here and is exercised by a test or by the fixture build; it does not mean the upstream student repositories have adopted it.

Schema

  • packages/schema — canonical envelope per ADR-001
  • Adapters from all four existing passport shapes — not built. The consolidation vendored and rewrote the code onto the canonical envelope rather than translating between four shapes at runtime. If the upstream repos are to keep running independently, they still need adapters
  • Fixtures + round-trip tests for each adapter — blocked on the above
  • Generate the fieldTiers map from the schema rather than maintaining both — DEFAULT_FIELD_TIERS lives in schema/src/envelope.ts; govern imports it and warns on any path not present in it, so a second map cannot drift in

Assessment

  • packages/assess/scorer.ts — the accumulation model, sole implementation
  • packages/assess/coverage.ts — promoted to a required passport field
  • packages/assess/forgery.ts — separate number, explicitly never merged
  • Lint rule or test that fails if a score renders without its coverage class — not done as specified. coverage.test.ts proves the type carries no score field, which stops the two being merged in data. Nothing stops a future component rendering a bare number in the UI

Evidence

  • packages/evidence — one connector interface
  • Merge Tavily + Wikidata (agent) with Met / AIC / MoMA / Wikipedia / Europeana (provenance-search) — eight connectors behind one interface
  • Verify behavioural equivalence before switching either repo over — the upstream repos have not been switched over, so this has not been tested
  • Generalise the isGeneralKnowledge: true labelling to all unsourced assertions
  • Record the MoMA static-export snapshot date in every passport that uses it — no snapshot date is recorded anywhere

Governance

  • packages/govern — envelope + canSee + redactForRole
  • apps/api — every response role-parameterised; redaction at the boundary only
  • Test that no client-reachable endpoint can return an above-tier field — verifyBoundary.test.ts. It proves this of deliver(), which is the only way either app emits a passport
  • Re-verify EAS notarisation against the canonical envelope's contentHashnotarise.test.ts

Issuance & lifecycle

  • packages/issue/wallet.ts — secp256k1 / EIP-191 / did:pkh
  • packages/issue/institution.ts — ECDSA P-256 CA chain
  • packages/issue/canonicalise.ts — one canonicalisation both classes sign over
  • packages/lifecycle — StatusList2021 revocation, reinstate, amendment
  • Human review queue with persistence — the queue is in-memory only
  • Persistent duplicate detection — session-only is insufficient for a registry

Demo

  • One real object end-to-end through all seven stages — pnpm fixtures runs the real pipeline over four cases; every score on the site is generated, not written
  • Shown in all four role views — /disclosure shows one signed record to all five roles with a leakage proof
  • Same object visible as a VANGO stamp with zero above-public leakage — VANGO is not vendored (ADR-007, now reopened)

P2 — Correctness and honesty fixes carried from the student repos

  • Case count contradiction — §07/§13 say five cases, §08 implies fifteen, §06 cites an uncounted sixth (Getty Bronze). Reconcile to one number (arts-provenance-agent)
  • "Risk 12/100" reads backwards — fix in the interface, not as a caveat (arts-provenance-agent, peer review minor m1)
  • Lead the payment layer on merits, not hackathon origin (peer review minor m2)
  • Arithmetic error in the recompute-by-hand section — 3 × 30 leaves 10, not 0. Becomes moot under ADR-002 but the paper still needs rewriting (provenance-search)
  • "Free public source" describes a commercial service — Tavily is keyed and commercial; reconcile the wording (provenance-search)
  • Forgery risk vs. provenance confidence — two different numbers on the same scale, currently unlabelled. Readers will conflate them (digital-passport-artworks)
  • Passport name collision — visitor passport vs. object passport (VANGO)

P3 — Open research questions

These are not engineering tasks. Several are the Phase 1 and Phase 2 work the prototype deliberately skipped, now returning as blockers.

  1. Is the orthogonal source-community role correct? It asserts that source communities see claim status and custodianship but not enforcement internals. Decided by one implementer under time pressure with no consultation. **The weakest claim in the plan.** Needs a real source community to review it.
  2. Who operates the governance root for the institutional issuer class, and what process revokes an accreditation?
  3. Is "never clear, only no-evidence-found" the right public posture? It is a legal and reputational position, not only a technical one.
  4. What is the legal status of a pseudonymous registration under Italian and EU law? Does registering constitute an admission?
  5. Does GDPR erasure conflict with on-chain notarisation even when only a hash is attested?
  6. How is off-chain availability guaranteed? A lost record makes its attestation unverifiable. Attesting a hash is only useful if the preimage survives.
  7. What happens when a claim is filed against a registered object? No repo models the restitution workflow.
  8. Who pays for register lookups at scale? x402 answers the mechanism; it does not answer the economics.
  9. Should scores be public at all, or only visible above a certain role tier? A public score on a contested object is itself an act with consequences.

P4 — Deferred from the framework prototype

Carried from dpa-prototype/BACKLOG.md, unchanged in priority.

Auth & identity

  • Real SIWE authentication; role claims are currently mocked
  • Organisation membership verification for museum / enforcement roles
  • Key recovery for pseudonymous holders

On-chain

  • Register the EAS schema on-chain rather than assuming a UID
  • Revocation path for attestations
  • Gas sponsorship so holders do not need testnet ETH
  • Mainnet migration plan (explicitly out of scope for v0.4)

Exhibit

  • Replace the procedural geometry with a real cultural-object scan. The DamagedHelmet.glb placeholder is gone — /exhibit now generates its geometry — but a real scanned artefact is still the point, and Stage 0 is how one gets here (P0 §6)
  • Point-of-interest schema — position, haptic profile, voice intents, DPA field refs
  • Voice intent grammar mapped to confidentiality tiers (some intents are museum-tier)
  • Provenance-trail timeline built from on-chain attestations
  • Source-community audio statement, gated on a consent attestation
  • Real haptics and voice — HopeOS provides neither. It is a single HTML file (MediaPipe Hands + a Three.js particle globe). The IRenderer abstraction is where real hardware would land

Physical layer

  • RFID / NFC / microdot tagging — stub only, no implementation anywhere

Testing & DevOps

  • End-to-end tests across the full pipeline — pnpm fixtures exercises all seven stages at build time and CI fails if it breaks, but there is no test that asserts on the result
  • Contract tests beyond the existing 4
  • CI across the monorepo — typecheck, 342 tests, static export, and a Pages deploy on every push to main
  • Golden-file tests for the redaction boundary — verifyBoundary.test.ts proves the property; no committed golden files
  • Load characteristics of the evidence service — currently unknown

Not in v0.4, deliberately

  • Real PII handling and a GDPR audit
  • Real KYC, real custody, real money — x402 stays on testnet USDC
  • Fractional ownership, lending, insurance integration
  • Mainnet or any chain other than Base Sepolia
  • The full restitution workflow
  • Merging VANGO (ADR-007)
  • New evidence sources beyond consolidating existing ones — the coverage fix is P0 to name, but it is v0.5 work to ship, because it requires validation against known cases rather than just new connectors
Contents

Attribution

The v0.4 monorepo is a consolidation. Most of the code in packages/ was written elsewhere, by other people, and vendored here. This file records who wrote what.

Every vendored source file also carries a PROVENANCE: header naming the repository and module it came from. If you find code here without one, that is a bug — please open an issue.


Upstream repositories

Ethical-Tech-CoLab/arts-provenance-agent — MIT

The largest single contributor to v0.4, and the only part of the programme that has been through independent academic peer review (22 Jul 2026, verdict minor revisions).

Vendored intoOriginal
packages/schemaschema/passport.ts — adopted as the canonical envelope (ADR-001)
packages/assess/src/scorer.tssrc/agent/assessRisk.ts — the canonical accumulation scorer (ADR-002)
packages/assess/src/coverage.tssrc/lib/coverage.ts — the coverage model (ADR-003)
packages/evidence/src/connectors/, registers.tssrc/tools/ — Tavily, Wikidata, tiered register checks
packages/issue/src/wallet.tssrc/lib/signing.ts, src/wallet/ — secp256k1 / EIP-191 / did:pkh
packages/paymentssrc/agent/payForCheck.ts, src/lib/spend.ts, src/vendor/server.ts — x402
apps/agentsrc/agent/orchestrator.ts, parseIntent.ts, timeline.ts

Ideas adopted wholesale, which matter more than the code:

  • The coverage model. That absence of evidence and absence of coverage produce the same low number, and that only one of them is about the object. This is the best idea in the programme.
  • **"No register check returns clear."** The strongest available negative is no-evidence-found (ADR-009).
  • Per-claim evidence provenance. No source, no claim.
  • Signing the caveat alongside the verdict, so a result cannot be stripped from the qualification that makes it readable.
  • Honesty about its own source bias — that the permitted-source list searches worst exactly where the motivating harm lives. Carried into packages/evidence/src/sourceBias.ts rather than quietly dropped.

Ethical-Tech-CoLab/provenance-search — MIT

Vendored intoOriginal
packages/evidence/src/connectors/The Met, Art Institute of Chicago, MoMA, Wikipedia, Europeana connectors
packages/identity/src/identify.tsGemini Vision identification (/api/identify)
apps/web field captureCamera / Field Mode UX
packages/evidence general-knowledge policyThe isGeneralKnowledge: true labelling pattern, generalised (ADR-008)

Its computeConfidenceScore was deliberately not ported — see ADR-002. That is a decision about consolidation, not a judgement about the work.

Ethical-Tech-CoLab/digital-passport-artworks — MIT

Originally by @ChristineLumen.

Vendored intoOriginal
packages/identity/src/fingerprint.ts, similarity.ts, forensics.tsSHA-256 + perceptual dHash + angle count; Harris corners, SIFT-style descriptors, RANSAC; ELA, noise floor, spectral/edge energy
packages/issue/src/institution.tsThe ECDSA P-256 governance root → federated root → issuing CA chain (ADR-004)
packages/lifecycle/src/statusList.tsStatusList2021-style signed revocation bitstring
packages/lifecycle/src/review.tsThe human review gate and its routing thresholds

It is the only upstream project with a complete lifecycle — issue, revoke, reinstate, verify — and the only one that modelled accreditation rather than self-assertion. Both are load-bearing in v0.4.

Ethical-Tech-CoLab/VANGO — MIT

No code vendored. VANGO remains its own repository and its own product, by design (ADR-007). It is the reference consumer of the public disclosure tier rather than a module of the core.

Its contribution to v0.4 is conceptual: it is the only consumer-facing surface in the programme, the only multilingual one (EN/FR/IT), and the only physical→digital bridge. Its catalogue entry BURA01 — Bura Ceramics, Niger, AABC — pointed at the object now used as the hero case throughout this repo.

yorkerhodes3/dpa-prototype — the framework reference implementation

Vendored intoOriginal
packages/govern/src/redact.ts, envelope.tslib/dpa.ts, schemas/confidentiality-envelope.json — the confidentiality envelope
packages/schema/src/roles.tsThe role model, including the orthogonal source-community role
packages/govern/src/notarise.tslib/eas.ts — EAS on Base Sepolia (ADR-006)
contracts/DPARegistry.sol and its Foundry tests
apps/web/app/exhibitexhibit/src/renderer.ts, hopeos-adapter.ts

Its mocked INTERPOL/ALR lookup was deleted and replaced by the real evidence service (ADR-008).


Third-party

  • HopeOS by Hannah Zhao / @kennyAIrepo — the gesture-driven particle-globe renderer the exhibit's IRenderer adapter targets. HopeOS is a single-file MediaPipe Hands + Three.js visualisation; the exhibit's haptic and voice ambitions are not backed by it, and the abstraction exists so real hardware can be substituted later.
  • Khronos glTF Sample Models — CC0. DamagedHelmet.glb is a placeholder standing in for a real cultural-object scan.
  • @noble/curves and @noble/hashes by Paul Miller — MIT. Chosen over node:crypto so the crypto runs unchanged in the browser.
  • Zod, Next.js, three.js, Foundry, EAS.

Research and framework

The programme is defined by Annex A — Preliminary Research Framework (February 2026), produced for AABC / Ars Pro Mundo with SDA Bocconi. The four-layer passport model (descriptive / legal / blockchain / physical), the role-gated disclosure requirement, and the "From Shadow to Light" framing are from that document.

Three research reports remain with their original repositories and are cited rather than absorbed:

  • DPP-Paper.md and PEER-REVIEW.mdarts-provenance-agent
  • Provenance-Search-Paper.mdprovenance-search
  • VANGO-Paper.mdVANGO

Licence

Vendored code retains its original MIT licence. New code in this repository is MIT. Planning and research documents in docs/ are CC BY 4.0.

If you contributed to any upstream repository and want your attribution changed, corrected, or removed, please open an issue — it will be actioned.

← Back to the overview