convert an Archivematica AIP an OCFL object
  • Go 95.1%
  • Shell 4.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Justin Simpson 21d9649813 Docs: link umbrella documents absolutely; trim CLAUDE.md to basics
Relative links out of the repository do not resolve on the public forge.
The README's contributing section now matches CONTRIBUTING.

Link: #1
Signed-off-by: Justin Simpson <jsimpson@artefactual.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:35:58 +00:00
.forgejo/workflows Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
cmd Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
docs Docs: link umbrella documents absolutely; trim CLAUDE.md to basics 2026-09-22 20:35:58 +00:00
internal Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
LICENSES Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
reports Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
scripts Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
.gitignore Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
CLAUDE.md Docs: link umbrella documents absolutely; trim CLAUDE.md to basics 2026-09-22 20:35:58 +00:00
CONTRIBUTING.md Docs: say the repository is read-only; issues welcome, no pull requests 2026-09-22 16:47:50 +01:00
DCO Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
go.mod Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
go.sum Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00
README.md Docs: link umbrella documents absolutely; trim CLAUDE.md to basics 2026-09-22 20:35:58 +00:00
REUSE.toml Public baseline (article: experiment one) 2026-09-21 16:30:28 +01:00

provenant-import

The legacy on-ramp for Provenant: it ingests an Archivematica AIP and produces the OCFL object that Provenant attestations bind to.

Status: prototype; one package at a time; not integrated with shipped products.

It is one of two producers in Provenant's two producers, one substrate, one verifier architecture (ADR-06):

Legacy path:  Archivematica AIP (.tar/.7z/dir) --> provenant-import --> OCFL object -+
Direct path:  live workload (Spike 02) -------------------------------> OCFL object -+
                                                                                     v
                                              provenant-attest (METS -> attestations)
                                                                                     v
                                              provenant-verify (UNCHANGED, both paths)

provenant-import absorbed the conversion core of the now-retired a11a2ocfl PoC: 7z/tar/dir extraction, BagIt parse, METS capture, and the OCFL builder (sha512 manifest + a fixity bridge from the bag's native manifest). The lossy JSON-LD PREMIS step and the home-grown OCFL validator were dropped: Provenant reads PREMIS-in-METS directly (in provenant-attest) and validates with stock rocfl.

In the article

This repository is experiment one of the article "Computed and Asserted": copy a real Archivematica AIP, whole, into an OCFL object, then check every payload file against the manifest the AIP itself carries. Figure 1 of the article is the equivalence table this tool writes.

The command:

provenant-import --profile b --reports work/reports aip/CSVmultiLevel.tar work/object

What that run recorded (captured 2026-09-04, the run figure 1 was rendered from):

Source payload entries Preserved by digest Dropped per profile Unaccounted
19 18 1 0
  • 19 payload files are listed in the AIP's own manifest-sha256.txt.
  • 18 are in the OCFL object under a matching digest.
  • 1 is dropped on purpose, and the drop is recorded: the transfer METS (data/objects/submissionDocumentation/transfer-CSVmultiLevel-<uuid>/METS.xml). It is submission documentation from the transfer, not the preservation payload. The AIP's own METS is kept and round-trips (the report says true).
  • The import took 0.24 s of wall clock in that run: under half a second.

import-report.md, written next to the object, is the record of the equivalence: the counts above, the dropped file by name with the reason, and the bag wrapping files that profile b leaves behind. Nothing is unaccounted for.

The input is an Archivematica AIP made from the CSVmultiLevel sample transfer in archivematica-sampledata. It is a 15 MB tar and is not part of this repository.

Usage

provenant-import [--profile b] <input.tar|input.7z|input-dir> <output-dir>

Import profiles (ADR-06 section 2a)

What of the source AIP the OCFL object preserves is an operator choice. In every profile the source METS stays authoritative (SC-9); they differ in how much of the original bag wrapping travels with the object.

--profile What the object holds When
b (default) METS + digital objects (lean preservation view) the usual case
c as b, plus the native manifest-*.txt / tagmanifest-*.txt as preserved evidence keep the source's own fixity record
a v1 = the opaque original archive; v2 = Provenant's extracted output bit-level fidelity to the submission; uses OCFL versioning (P9)

See docs/import-build-decisions.md for the rationale behind the default and the profile-(a) subject-granularity model.

Import-event facts + equivalence report

Alongside the OCFL object, each import writes two files to --reports (default reports/):

  • import-event.json - the machine-readable FACTS of the crossing (object id, profile, source AIP identity + sha256, a deterministic event id, and an equivalence check: every source payload entry is preserved in the object by digest, or accounted for as an intentional drop; plus the METS round-trip and the bag wrapping dropped per profile). provenant-import does not sign; provenant-attest -import-event import-event.json reads this record and signs it as one PreservationEvent (ADR-05 keeps signing in attest).
  • import-report.md - the human equivalence/import report. It doubles as the delete-after-verify safety record: it shows that every byte the source bag certified is either present in the object (verifiable by digest) or listed as a reasoned drop, so the source AIP can be deleted once the signed attestations verify.

Both are per-run output and are gitignored.

Build & test

The module targets Go 1.26.

Building the binary. go build ./... is a compile check only: it matches several packages (the internal/... libraries and the command), so Go compiles them to confirm they build and then discards the executables; no binary is written. To produce the actual binary, build the command package:

go build ./cmd/provenant-import                         # writes ./provenant-import (cwd)
go build -o bin/provenant-import ./cmd/provenant-import  # explicit output path
go install ./cmd/provenant-import                        # installs to $GOBIN, else $GOPATH/bin (~/go/bin)

The /provenant-import build output is gitignored. Run it with:

./provenant-import --profile b <input.tar|input.7z|input-dir> <output-dir>

Testing.

go test ./...

That runs on a bare clone: nothing needs to be staged. The reader, the three profiles, the import-event facts and the CLI are tested against a small synthetic AIP that internal/aip/aiptest builds in a temp dir as a directory and as a .tar. The .7z path uses one committed synthetic fixture of about 1 KB; its provenance note says how it was made. Where rocfl is installed the tests also validate every object they build with it.

Testing against real AIPs (opt-in). Real Archivematica packages are not part of this repository. The opt-in cases run the same code over three specific packages, found by name under the directory that PROVENANT_TESTDATA names:

PROVENANT_TESTDATA=/path/to/staged go test ./...
PROVENANT_TESTDATA=/path/to/staged scripts/acceptance.sh   # needs rocfl

The _real test cases and the acceptance script look for these paths:

Path under $PROVENANT_TESTDATA Form What it exercises
teste-cd7634fb-98bc-40f3-ada2-60a78a1be329/ extracted BagIt directory directory input; 42 payload entries, 41 preserved, 1 dropped
aips/CSVmultiLevel-76b9e633-de6e-437c-a3d3-80f7f6b2c48c.tar POSIX tar the article's AIP: 19 / 18 / 1
aips/PreservationCopies-a03ae51c-0c87-4abc-9269-3221c6c932e2.7z 7z md5-only manifest, so no sha256 fixity bridge (ADR-01 case 3)

With the variable unset those cases are skipped and say so. You do not need all three: a package that is not staged is skipped by name. A directory that holds none of them fails the tests, so a mistyped path cannot pass for coverage. scripts/acceptance.sh runs every profile over every staged package, validates each object with rocfl, and writes reports/acceptance.md.

CI. CI of record runs on the maintainers' sovereign runner; the public copy runs the same workflows where the host allows. The workflows are in .forgejo/workflows/: ci.yml (build, test, vet, gofmt, govulncheck) and compliance.yml (REUSE lint and the DCO sign-off check).

AI assistance

This code was written with AI assistance (Anthropic's Claude), and that is disclosed: AI-assisted commits carry a Co-Authored-By: trailer. The policy mirrors the Linux kernel's. AI-assisted contributions are permitted and must be disclosed. An AI cannot provide the Signed-off-by: a human signs off every commit on main and thereby takes responsibility for the contribution's origin and licensing under the DCO. Nothing reaches main on an AI's authority alone (ADR-08).

Licensing & contributing

REUSE-compliant: code is Apache-2.0, docs are CC-BY-4.0 (ADR-07). Every file carries an SPDX header or is declared in REUSE.toml.

You are welcome to fork the repository, try it out, and open issues. For now the repository is read-only except to its maintainer, and pull requests are not accepted. CONTRIBUTING.md says how commits here are made.