5. Package and deposit¶
agentprov package has two modes:
- seal mode turns a working corpus (
DIR.partial) into a sealed corpus version (DIR) and prints its root digest; - deposit mode (
--deposit OUT) builds a deposit draft (deterministic archives plus metadata) from one or more sealed corpora.
package never uploads. It works offline and starts no upload. A deposit draft is a directory on your disk;
publishing it (for example on Zenodo, which mints the DOI) is a separate, manual step that only the project owner
takes. An optional helper script can create an unpublished Zenodo draft from the directory
(below); it never publishes. AgentPROV corpora get their own Zenodo record and
concept DOI, never a new version of AgentDojo-PROV's.
Before you seal¶
Sealing is release mode. It refuses unless:
- The corpus was imported or derived without problems that block release: no
incompletesnapshot, no--skip-prov-conformancemark, and an interpreter without an advisory (3.14.8 or later). - You have a corpus spec. The spec is a directory
corpora/<corpus-id>/holdingcorpus-spec.toml(identity, sources and pinned snapshots, derivation settings, licence),CARD.md(the hand-written parts of the datasheet) andCHANGES.toml(the changelog; every corpus version needs an entry). The shared decided statements filecorpora/datasheet-statements.tomlmust sit beside it, one level up. The repository'scorpora/directory holds the specs of the declared corpora;--spec PATHnames another one.
A spec can also name the corpus's persona files ([scan] personas, each with its path and sha256): the
reviewed lists of e-mail addresses, as digests only, that the release scan clears and the seal withholds
(below). Sealing copies each into rules/personas/, so it is covered by the root
digest. It refuses a file that is missing, does not match the spec's digest or does not parse, and an observed list
that names another corpus (package.refused.persona). The committed files are in corpora/personas/.
3. The checkout is clean. package records the producer's git commit, and refuses when the tree has uncommitted
changes:
agentprov: error [package.refused.git]: package runs in release mode: the producer commit must be known and its tree clean (H.2.7)
The same holds for the checkout that built the corpus: a corpus whose manifest records a build step from a dirty
tree is refused (package.refused.build_dirty), because its recorded commit would not describe the code that ran.
Rebuild it from a clean checkout. A build from an installed wheel (no git checkout) is not refused; the datasheet
shows why no commit is recorded, and a corpus built before commits were recorded seals with "building commit not
recorded".
- Upstream licence files are available. A licence file the spec names (
[licence] files) is taken from the corpus's raw store when the import stored it at the pinned path; otherwise give it with--licence-file NAME=PATH.NOTICE.mdrecords which files were supplied at sealing.
For the demo, a spec specs/demo-agentdojo/ was written beside a copy of corpora/datasheet-statements.toml (in
specs/), naming the pinned snapshot, agentdojo-upstream@2, agentdojo.suites@2 and one licence file. The licence
text supplied below is a placeholder for the demo; a real corpus carries AgentDojo's LICENSE from the pinned commit.
Worked example: sealing the demo corpus¶
A dry run computes and checks everything and writes nothing:
$ agentprov package --corpus corpora/demo --corpus-version 0.1.0 --spec specs/demo-agentdojo/corpus-spec.toml --licence-file LICENSE=LICENSE.demo --release-date 2026-10-22 --dry-run
agentprov package: units=3 plan=seal
package: finished in 242ms (exit 0)
units 3 of 3: ok 3
dry run: demo-agentdojo 0.1.0 would seal with content digest sha256:e8d2ac2efb731e94f10bdfad0847985e0020032ed6ef77c523905a4ec130c9f6
Then seal:
$ agentprov package --corpus corpora/demo --corpus-version 0.1.0 --spec specs/demo-agentdojo/corpus-spec.toml --licence-file LICENSE=LICENSE.demo --release-date 2026-10-22
agentprov package: units=7 plan=seal
package: finished in 334ms (exit 0)
units 7 of 7: ok 7
sealed demo-agentdojo 0.1.0: 3 executions, 40 files
content digest sha256:e8d2ac2efb731e94f10bdfad0847985e0020032ed6ef77c523905a4ec130c9f6
root digest sha256:2d5372e2db9b555f0c2c552206363b8694808fe124457b875a1e9091429dcba3
corpora/demo.partial/ is now corpora/demo/, with checksums.sha256, the full corpus.json, DATASHEET.md,
CHANGELOG.md, LICENSES/, and record copies of the rule catalogues, profiles and spec in rules/ and the schemas
in schemas/. The copies are a record only: verify and recompute load catalogues from the installed package.
Sealing is deterministic: two seals of the same working tree with the same inputs give the same digests.
--release-date fixes the release date (otherwise SOURCE_DATE_EPOCH, otherwise none).
The two digests¶
- The content digest covers the corpus's contents.
- The root digest is the sha256 of
checksums.sha256, which lists every file. It identifies this exact sealed tree. Publish it outside the corpus (the paper, the release notes, the annotation of the tagcorpus/<corpus_id>/v<version>), so that anyone can check a copy withagentprov verify --release --root-digest(chapter 4).
The datasheet¶
DATASHEET.md is generated at sealing. Every number in it is computed from the manifest, the index and the register,
or comes from a decided statement with its source. Its sections are:
| Section | Holds |
|---|---|
| Motivation, Maintenance | from CARD.md |
| Composition | counts by unit, status and completeness; each snapshot's pinned origin and raw policy; requested and served models with their evidence class |
| Uses | the decided statements that apply |
| Evidence and information loss | field-group coverage: for each group of fields, the share of executions whose fields are observed or derived; records by kind |
| Integrity and verification | digests, versions, the sealing commit and the building commits (or "building commit not recorded"), and how to verify |
| Known issues and validity caveats | issue codes and artefacts with counts |
| Distribution and licensing | licence class, licence files, withholding |
| Untrusted content | the prompt-injection notice |
CARD.md can use placeholders that sealing fills: {{v:/counts/executions}} (a value from the manifest) and
{{claim:raw_included}} (a statement from the claims library, checked against the corpus). agentprov validate --card
CARD.md lints a card before you seal.
Withholding at sealing¶
A working corpus seals as a corpus version that withholds, rather than refusing, when it has purge tombstones, a
snapshot with raw policy withhold, or a source-profile [withhold] policy. The affected raw objects, and the
executions derived from them (unless the rule's scope is object), are moved into the private sidecar
DIR.withheld/, never deleted. WITHHELD.json in the sealed tree lists each withheld object's digest and pinned
origins and each withheld execution's id, and nothing else; the datasheet states the counts and the reasons.
verify --level chain in release mode accepts a tombstone only when WITHHELD.json lists it.
E-mail addresses are withheld the same way, by digest. A persona-file group of class withhold lists the addresses
that only a model wrote, at a public mail provider (they could be a real person's): sealing withholds every execution
that holds one, with reason personal_data. A group of class withhold-object lists addresses found only in raw files
that no execution uses: those objects are withheld, and no execution is affected. The rule matches digests, never a
literal address. A ledger that holds such an address rule is written as agentprov-withheld/0.4 (other ledgers keep
their earlier versions, so their bytes do not change), and verify --level chain and the deposit builder apply the
rule again to check that nothing it matches was left in the sealed tree.
Deposit drafts¶
The release scan comes first¶
The deposit builder refuses a corpus without a release scan for the same root digest:
$ agentprov package --deposit deposit --corpus corpora/demo --metadata release.toml --release-date 2026-10-22 --dry-run
agentprov: error [release.refused.scan_missing]: demo-agentdojo: no release scan for this corpus; run tools/release_scan.py on the sealed corpus and triage every finding (G.9.4)
The scan runs outside the CLI, because the builder starts no process. It needs gitleaks 8.30.1 (the pinned version; any other is refused):
$ uv run python tools/release_scan.py corpora/demo
release_scan: 0 finding(s) {}; root digest sha256:2d5372e2db9b555f0c2c552206363b8694808fe124457b875a1e9091429dcba3; written to …/corpora/demo.release-scan.json
Archives among the raw objects (zip, tar and the other stored containers) are read decoded, member by member, without extracting anything, and so is every archive the sealing scan checks for withholding. Several archives are read at once, up to the host's default worker count (D-890); one archive's members are still read in order, so the findings are the same at any count.
It searches every file, raw objects included, for secrets (gitleaks plus AgentPROV's own token shapes), your home
path and host name, third-party workstation account paths and e-mail addresses (the email-address rule; a finding
never holds the address). Addresses at RFC-reserved domains (example.com, .test, ...) are cleared by the tool. The
others are cleared only by the persona files that the corpus's spec names ([scan] personas; digests only, sealed
with the corpus and bound to its root digest), whose groups say where each address first appears:
| Class | The address is | Outcome |
|---|---|---|
upstream-data |
in the benchmark's own published files at its pinned commit (its run outputs excluded) | cleared, synthetic-benchmark |
benchmark-input |
first in input the benchmark supplied to an execution (a task, a tool result, an injection) | cleared, synthetic-benchmark |
model-generated |
only in model output, at a domain that is not a public mail provider | kept, triaged model-generated and disclosed in the datasheet |
withhold |
only in model output, at a public mail provider | its executions are withheld at sealing (above) |
withhold-object |
only in raw files that no execution uses | those raw objects are withheld at sealing |
file-name |
not an address but a file name of that shape (an image's @2x suffix) |
cleared, false-positive, for that corpus only |
Every other address is a finding. Chapter 9 shows how to review them by
address and how tools/scan_personas.py and tools/scan_origins.py write the lists. Every finding must be triaged in a triage file
(--triage TRIAGE.toml) as real, synthetic-benchmark, false-positive or pinned-account; a real finding must
be withheld before the corpus can be deposited. See chapter 9.
Building the draft¶
The deposit metadata is a small TOML file:
title = "AgentPROV user-guide demo deposit"
description_preamble = "A deposit draft built for the user guide; never uploaded."
keywords = ["demo"]
[[creators]]
name = "Example Creator"
$ agentprov package --deposit deposit --corpus corpora/demo --metadata release.toml --release-date 2026-10-22
agentprov: finished in 219ms (exit 0)
deposit draft: wrote 5 archive(s) for 1 corpus version(s) to deposit
demo-agentdojo 0.1.0: root digest sha256:2d5372e2db9b555f0c2c552206363b8694808fe124457b875a1e9091429dcba3; licence class MIT
Nothing has been uploaded or published.
The draft directory holds:
| File | What it is |
|---|---|
<corpus>-v<version>.meta.zip |
the corpus's manifest, index, register, checksums, datasheet, licences and record copies |
<corpus>-v<version>.raw.001.zip, .aer.001.zip, .sem-<profile>.001.zip, .prov-<spec>.001.zip |
the data, split by layer (and on shard boundaries above --max-archive-bytes) |
DEPOSIT-MANIFEST.json |
every archive with its size and digests |
deposit-metadata.json |
the record metadata to enter on the repository's page |
README.md, DATASHEET-<corpus>.md, CHANGELOG.md |
the human-readable description |
Archives are deterministic: members are sorted, carry the release date as their timestamp and fixed permissions, and use DEFLATE at a fixed level, so the same corpora and metadata give the same bytes.
Licence classes. A deposit refuses corpora of different licence classes (exit 4) unless you give
--allow-mixed-licence-classes, which is recorded; non-commercial data is never mixed in. Each copyleft source has its
own corpus.
Verifying a deposit¶
verify reads deposit archives without extracting them. Verify the whole draft directory, with the root digest:
$ agentprov verify --corpus deposit --release --root-digest sha256:2d5372e2db9b555f0c2c552206363b8694808fe124457b875a1e9091429dcba3
agentprov verify: units=1 mode=archive plan=level chain
…
agentprov verify: level chain (release mode): passed; 0 failed, 0 unverified or tombstoned
demo-agentdojo-v0.1.0: root digest sha256:2d5372e2db9b555f0c2c552206363b8694808fe124457b875a1e9091429dcba3
A .meta.zip on its own can be verified too, but it does not contain the data archives, so their members are
reported archive.not_present. Without --release that is a pass with unverified checks; with --release an
unverified check fails (exit 1).
Creating an unpublished Zenodo draft¶
The draft-only uploader can turn a verified deposit directory into an unpublished draft on Zenodo. It is not an
agentprov subcommand but its own distribution, agentprov-zenodo (D-880), so the agentprov package itself never
talks to the network for a release. In a checkout it is already installed (chapter 1, the zenodo extra) and runs as
tools/zenodo_draft.py, as below; python -I -m agentprov_zenodo and agentprov-zenodo-draft are the same program
with the same options. The desktop app runs it too (Package › 9 Finish › Create Zenodo drafts…, below). It never
publishes, edits a published record or deletes anything: you review the draft in the web UI and press Publish yourself.
Run it in your own terminal; the steps below use placeholders, not real output.
- Make a token on the site you target. The sandbox (
sandbox.zenodo.org) and production (zenodo.org) need separate accounts and tokens. The scopedeposit:writeis enough for a new draft;--new-version-ofalso needsdeposit:actions(Zenodo's documentation lists it for new versions, and it also covers publishing, which this script never does), so give a sandbox token only the scopes you need. Put the token in your shell's environment yourself:ZENODO_SANDBOX_TOKENfor the sandbox,ZENODO_TOKENfor production. Or store it once in the OS keychain under the same name and add--keychainto each command (below). The script never asks for it, and prints, logs and records neither it nor theAuthorizationheader. - Check offline first.
--checkneeds no token and sends nothing; it verifies the directory and shows exactly what would be sent:
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --check
The offline checks refuse a directory that holds a file the deposit manifest does not list, or lacks one it lists, or
whose size, sha256 or md5 differs, or that does not pass archive-mode verify in release mode. Pass the root digest
you published outside the deposit with --root-digest sha256:<hex> (one per corpus). A deposit of more than one
licence class, or with a licence the script cannot map to a Zenodo licence id, is refused.
3. Try the sandbox first. Zenodo's sandbox can be cleaned at any time (its documentation says test DOIs use the prefix 10.5072; the trial of
2026-10-23 saw a reserved DOI with the prefix 10.5281, so judge a record by its host, not by its DOI prefix):
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --create-draft --root-digest sha256:<hex>
--target has no default. The script shows the host, every file with its size and sha256, the title, version,
licence and creators, and "this creates an unpublished draft; nothing is published". It needs a terminal and
asks you to type a phrase that names the host; there is no --yes.
4. Check the draft at the address it prints. Each upload's md5 is compared with the manifest, and the draft's file
list is read back at the end. A mismatch stops the run and is reported; nothing is deleted.
5. Then production, the same command with --target production. The last line is
Draft created, NOT published. Review it at <address> and press Publish yourself.
The script writes a receipt <deposit directory name>.zenodo-<target>-draft-receipt.json beside the deposit
directory (never inside it: the directory is checksummed). While it exists, a second create is refused. If an upload
was interrupted, run the same command with --resume: it uploads only the files the draft lacks, to the draft the
receipt records, after checking that it is still unpublished. A first AgentPROV release is always a new deposition,
never a new version of AgentDojo-PROV's concept. A later version uses --new-version-of <deposition id> (the id of the
latest published version) and only works when the release metadata records the AgentPROV concept DOI; Zenodo copies the
previous version's files into the new draft, which this script never deletes, so remove them in the web UI before you
publish.
Sandbox trial checklist¶
Do this once, on the sandbox, with a small synthetic corpus, before any production run. Every command is yours to run in your own terminal; nothing here publishes anything, and no deposit of real corpora happens until you decide it (the standing rule: no Zenodo deposit is made without your explicit instruction). Ids below are placeholders.
- Sandbox account and token (you). Register at
sandbox.zenodo.org(a separate account fromzenodo.org), open Applications, create a personal access token with the scopedeposit:writeonly, and keep it to yourself. Nobody else needs it, and no assistant session should see it. - Put it in the environment without leaving it in your history. In the terminal you will run the tool from:
$ read -rs ZENODO_SANDBOX_TOKEN && export ZENODO_SANDBOX_TOKEN
(type or paste the token; nothing is echoed). Use a new terminal for the production run later, with ZENODO_TOKEN.
3. Build a draft from the demo corpus (synthetic, MIT; the worked example above): seal it, run the release scan, write
release.toml, then build the draft, with your own paths in place of the placeholders:
$ agentprov package --corpus corpora/demo --corpus-version 0.1.0 --spec specs/demo-agentdojo/corpus-spec.toml --licence-file LICENSE=LICENSE.demo --release-date YYYY-MM-DD
$ uv run python tools/release_scan.py corpora/demo
$ agentprov package --deposit DEPOSIT_DIR --corpus corpora/demo --metadata release.toml --release-date YYYY-MM-DD
Note the corpus's root digest from the sealing output (sha256:<hex>).
4. Look before you send. No token and no network are needed for this:
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --check --root-digest sha256:<hex>
Read the file list, the licence (mit), the title and the creators. Anything listed as "NOT sent" has no Zenodo
relation; decide whether that matters.
5. Create the draft:
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --create-draft --root-digest sha256:<hex>
After the consent screen, type exactly create unpublished draft on sandbox.zenodo.org. The last line should read
Draft created, NOT published. Review it at https://sandbox.zenodo.org/deposit/<ID> and press Publish yourself.
Exit code 0. A receipt DEPOSIT_DIR.zenodo-sandbox-draft-receipt.json appears beside the directory.
6. Check the draft on the sandbox page (and do not press Publish):
- the record is a draft (not published, no DOI registered; a reserved DOI is normal);
- the title, version, creators, description (paragraphs), keywords and the licence (MIT; Zenodo stores it under its
own id mit-license) are the ones you saw;
- the files are the ones on the consent screen, with the same sizes; the md5 Zenodo shows for each equals the md5
in DEPOSIT-MANIFEST.json;
- any HTTP error text the tool printed (for example "Not a valid choice" for the licence) is worth copying back: it
means a value the tool took from Zenodo's documentation, not from the live service, is wrong.
7. Try --resume. Delete one file from the draft in the web UI (a Ctrl-C during an upload is another way, but the
tool has no safe pause point between files), then:
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --create-draft --resume --root-digest sha256:<hex>
Type exactly resume unpublished draft <ID> on sandbox.zenodo.org. Only the files the draft lacks are uploaded. Running
step 5 a second time instead is refused while the receipt exists: that refusal is the behaviour to see.
8. Clean up (you). Delete the sandbox draft in the web UI (the tool never deletes anything), remove the receipt
file, and unset ZENODO_SANDBOX_TOKEN (or close the terminal). The sandbox may be cleaned by Zenodo at any time.
9. What the trial of 2026-10-23 settled and did not (docs/reports/release/zenodo-sandbox-trial.md): the metadata
fields, the licence id mit, the md5 and size Zenodo returns for each upload, the receipt, the second-create refusal
and the draft's state were confirmed on the live sandbox. The 100-file and 50 GB limits, the other licence ids, a new
version of a published record and the error body shape were not tested (they are named constants, MAX_FILES,
MAX_FILE_BYTES, MAX_TOTAL_BYTES, LICENCE_IDS, at the top of zenodo/src/agentprov_zenodo/draft.py).
The collection trial of 2026-10-29 then confirmed communities, reserved DOIs, the cross-links (and that a second run
changes nothing), the membership refusal, the index-only umbrella with a copyleft datasheet linked rather than
copied, and the licence ids gpl-3.0-only and cc-by-4.0; the file and size limits are still untested.
A release of several records: communities, reserved DOIs and cross-links¶
A deposit holds one licence class, so a release of corpora under several licences is several Zenodo records. The
helper can place them in one Zenodo community and link them to an umbrella record (D-870). The umbrella holds no
corpus data: it is an index built with package --index once the part drafts exist (D-947; step 3 below).
- Name the community on every create (and on
--checkto see it):--community SLUG, repeatable, whereSLUGis the identifier in the community's address. The consent screen lists it. Zenodo sends the community's curators a request, which they accept or decline.
$ uv run python tools/zenodo_draft.py DEPOSIT_DIR --target sandbox --create-draft --community SLUG --root-digest sha256:<hex>
- The reserved DOI is in the receipt. Each draft has a DOI that is reserved, not registered until you publish. The
helper reads it from Zenodo and records it in the receipt (
reserved_doi, receipt formatagentprov-zenodo-draft-receipt/0.2, which also records the metadata sent). It asks Zenodo for one only when the draft has none, and stops if the DOI ever changes.--resumereads it back and never asks for another; give--resumethe same--communityoptions as the create, or it refuses. A receipt of the earlier format 0.1 records no DOI, so it can't be resumed or linked: finish that draft in the web UI. - Build the umbrella index once every part draft is
complete. It reads each part's receipt and the part's deposit directory beside it, and the sealed corpora those parts hold (each corpus in exactly one part):
$ uv run agentprov package --index INDEX_DIR --part PART_DIR.zenodo-sandbox-draft-receipt.json --part ... --corpus SEALED_DIR --corpus ... --metadata release.toml --release-date YYYY-MM-DD
INDEX_DIR holds README.md, COLLECTION-INDEX.json (each part's reserved DOI and record id, its licence class
and corpora; each corpus's version, content digest and root digest), one datasheet copy per corpus except those
under a copyleft licence (GPL, AGPL, LGPL: their row names the datasheet in the part record, with its digests, so
no copyleft-licensed corpus's text sits in the CC BY 4.0 index; D-949),
deposit-metadata.json (licence CC-BY-4.0) and DEPOSIT-MANIFEST.json of kind index. It has no archives. It
refuses a receipt that is not complete or has no reserved DOI, a part directory changed since its upload, and a
corpus whose digests differ from its part's. Draft it like any deposit, without --root-digest (the index holds no
corpus): zenodo_draft.py INDEX_DIR --target sandbox --create-draft --community SLUG. The consent screen says it is
a collection index.
4. Cross-link the drafts once every one is complete, with the index's receipt as the umbrella:
$ uv run python tools/zenodo_draft.py --target sandbox --cross-link --umbrella UMBRELLA_DIR.zenodo-sandbox-draft-receipt.json --member MEMBER_DIR.zenodo-sandbox-draft-receipt.json --member ...
Type exactly link <N> unpublished drafts on sandbox.zenodo.org. Each member gets isPartOf the umbrella's reserved
DOI, and the umbrella gets hasPart each member's. The members are not linked to each other: the deposit API has no
isRelatedTo. Any link to the software release belongs in release.toml [[related]] (for example
IsSupplementTo). The step reads every draft before it writes any. It refuses, with nothing written, a published
record, a receipt without a reserved DOI or not complete, a receipt given twice, related identifiers changed
in the web UI, and, when the umbrella is an index, members that are not exactly the part records its
COLLECTION-INDEX.json lists (by reserved DOI and deposition), or an index beside the receipt that is not the
uploaded one (D-949). It sends each draft's recorded metadata again, so make other web-UI edits after cross-linking.
Running it again changes nothing.
5. Trial it on the sandbox first (D-941 L-8, before any production draft):
$ tools/zenodo_collection_trial.sh COMMUNITY release.toml INDEX_DIR PART_DIR=sha256:<hex>[,sha256:<hex>] ... --corpora SEALED_DIR ...
It runs steps 1 to 4 in order: the offline checks and the creates of the parts, the index built from their receipts,
the index's check and create, a cross-link without the first part that must be refused (when there are two or more
parts), the cross-link and a second, idempotent cross-link. You type every phrase. It then prints a checklist: the
community is shown, each DOI equals its receipt's, the index holds no archive and no copyleft datasheet, and the
links are visible. It reads the token from ZENODO_SANDBOX_TOKEN in the environment (read -rs, as in the checklist
above), not from the keychain, and it works only on the sandbox. The user ran it to the end on the sandbox on
2026-10-29 (docs/reports/release/zenodo-sandbox-trial.md); the file and size limits, --keychain and the desktop
app's dialog are still untrialled.
Draft a collection from the terminal (D-949), in the order above: the parts, then the index from their receipts, then the cross-link. The desktop app's Create Zenodo drafts… is for single records: it drafts every line it is given and then cross-links them, so it cannot draft the parts before the index exists, and it asks before it cross-links two or more lines.
The token in the keychain, and the desktop app¶
--keychain reads the target's token from the OS keychain instead of the environment (D-880). Store it once with
agentprov keys set ZENODO_SANDBOX_TOKEN (or ZENODO_TOKEN; you type it at a prompt that does not echo) or in the
desktop app's Settings › Keys. The value never reaches argv, a log or the settings file.
The desktop app has the same steps (chapter 8 shows them):
- Settings › Zenodo stores what to prefill: the target to preselect, community identifiers, the AgentPROV concept
DOI and default creators. It holds no token.
- Package › 9 Finish › Write release.toml from the Zenodo settings… writes a release.toml template, and never
over an existing file. Fill in its title and description before Build Deposit.
- Package › 9 Finish › Create Zenodo drafts… takes one deposit per line (DEPOSIT_DIR sha256:ROOT; the first is
the umbrella, and an index umbrella is named alone, with no root digest). It runs the uploader for each deposit (--resume for an unfinished one), then --cross-link, all
with --keychain. With two or more lines it asks first, since a collection is drafted from the terminal (D-949).
- Terminal pane: the uploader runs in a terminal pane inside the dialog. Each run shows its usual screen, and you
type its phrase in the field below the pane, exactly as in a terminal.
- Drafts only: it creates and links unpublished drafts. You press Publish on Zenodo's website.
- Platforms: macOS and Linux (it needs a pseudo-terminal).
purge: removing a leaked object¶
purge removes one object, by sha256, from a working corpus's raw store (or one downloaded file from the fetch cache,
with --cache), and writes a tombstone. Executions that cite the object get the register code release.withheld, and
the next seal withholds them. Use it when a raw object turns out to hold a secret or personal data. It is
irreversible, so try it with --dry-run first:
$ agentprov purge sha256:294d9d585c9dbb37ec560570df2cebd26d91badbef84fc78c7d9d74b8b820bd9 --corpus corpora/demo-views --reason secret --dry-run
would remove sha256:294d9d585c9dbb37ec560570df2cebd26d91badbef84fc78c7d9d74b8b820bd9 (3008 bytes) from the working corpus corpora/demo-views.partial: objects/29/294d9d585c9dbb37ec560570df2cebd26d91badbef84fc78c7d9d74b8b820bd9.json
would write the tombstone in raw/tombstones.jsonl (reason secret, date 2026-10-22)
1 execution(s) cite the object; 1 new register entry release.withheld
ex_37evkqmflf2sqxvs7cc6sbgx6c
agentprov purge: dry run; nothing was removed or written
On a terminal, purge asks for confirmation. Elsewhere it refuses without --yes:
$ agentprov purge sha256:294d9d585c9dbb37ec560570df2cebd26d91badbef84fc78c7d9d74b8b820bd9 --corpus corpora/demo-views --reason secret
agentprov: error [cli.refused.unconfirmed]: purging is irreversible and needs confirmation: run it in a terminal and answer the prompt, or pass --yes (N.2.15)
After the purge, verify reports the object as tombstoned rather than missing:
$ agentprov verify --corpus corpora/demo-views.partial
…
tombstoned: raw.tombstoned at raw/objects/29/294d9d585c9dbb37ec560570df2cebd26d91badbef84fc78c7d9d74b8b820bd9: purged 2026-10-22 (secret); checks that need its bytes are skipped
A purged object is never stored or downloaded again: a later import or fetch that meets the same digest refuses it.
A sealed corpus is never purged (exit 4, corpus.sealed); purge the working corpus it came from, or a new corpus
version, and seal again. --reason is secret or personal_data.
Common errors¶
| Message | Cause and fix |
|---|---|
no corpus spec at '…/corpora/<dir>/corpus-spec.toml'; name it with --spec (exit 2) |
give --spec, or name the corpus directory like its spec directory |
corpora/datasheet-statements.toml is missing beside the corpora directory (exit 2) |
the spec must sit in <dir>/<corpus-id>/ with datasheet-statements.toml in <dir>/ |
[package.refused.git] (exit 4) |
commit or stash your changes; package records a clean producer commit |
[release.refused.scan_missing] (exit 4) |
run tools/release_scan.py on the sealed corpus and triage every finding |
[corpus.sealed] (exit 4) |
sealed corpora are never modified; work on a new version |
[cli.refused.unconfirmed] (exit 4) |
purge needs --yes when it cannot ask |