Verified plugins

ai.oz0/public-access

For Swedish public authorities - a public-access copy of records with masking a person decides, and an E-ARK archive package, from a signed tenant export, offline.

For: tenant administrators and plugin users.

ai.oz0/public-access helps a public authority disclose and archive the records its agents leave. Give it a tenant export, or a subject export containing one person's records.

Before reading the export, the plugin verifies its signatures and every file hash. It refuses an export whose content changed.

  • A public-access copy. It proposes what to mask in the records a request is for, a person decides each proposal, approves the review, and the plugin writes the masked copy. It never decides what is secret, and it never hands anything out.
  • An E-ARK archive package. It builds a Submission Information Package (SIP) of the export, to the European E-ARK specifications that e-archives accept, and checks any E-ARK package against their rules.

It reaches no network, needs no secret, and asks the cluster for nothing. The records never go to a model.

What the Swedish terms mean. Offentlighetsprincipen is the principle of public access: anyone may ask a public authority for its official documents (allmänna handlingar). The authority's registrar (registrator) finds the records and assesses whether any part is secret under the Public Access to Information and Secrecy Act (offentlighets- och sekretesslagen, OSL); what is secret is masked (maskering) and the rest handed out, and a refusal is a decision that can be appealed.The archive rules (arkivlagen, and the National Archives' regulations) say what an authority keeps and in what form; what may be disposed of is gallring.Whether a record is an official document, what is secret, and what must be archived are the authority's legal assessments. This plugin supports them; it makes none of them.

Install the command

The steps run where the export is, on the registrar's machine, offline:

Terminal
uv tool install ./plugins/public-access          # from a checkout of the repository, or the version the hub pins
oz0-public-access --version

The plugin also installs in a tenant like any other (oz0 plugin install hub:ai.oz0/public-access), which gives you the same steps as the functions of the tool oz0_public_access: see In the cluster.

Answer a request: export, mask, review, release

  1. Export the records. Make a tenant export with the jobs' content, for the period the request is about, and note the audit key's fingerprint, which you pin in every step:
    Terminal
    oz0 tenant export acme --content --since 2026-09-01T00:00:00Z --out ./export
    oz0 audit key                                            # the fingerprint to pin
    oz0-public-access verify ./export --key 3f9a…            # signatures and every file's hash
    oz0-public-access verify ./export --key 3f9a… --files    # what the export holds
    

    Making an export needs the tenant's tenant.export permission and, for the jobs' content, the content permission. Someone else's private chat is exported without what was said unless you give a reason, which is audited. A subject export, one person's copy of their records, is read the same way.
  2. Propose what to mask. Name the records the request is for, by job or by file of the export:
    Terminal
    oz0-public-access propose ./export --key 3f9a… --job job-123 --job job-456 \
      --table job_board --names ./names.txt --out ./review
    

    Every string value, every object key and every number in each record is scanned (a personnummer stored as a number, an e-mail address used as a key), and every candidate goes into review/review.json and a readable review/review.html, pending, with its place: the record, a JSON pointer, and whether it is a key or a number. Texts are compared in Unicode NFC, so a name typed with combining marks is the same name:
    CandidateFound by
    personnummer, samordningsnummerSwedish personal identity and coordination numbers, with their date and control digit
    email, phone, iban, payment cardThe guardrails plugin's baseline personal-data patterns
    nameEach name of the list you give with --names (one per line), whole words or in the genitive (Annas), any case
    free textA string of 200 characters or more, or of two sentences or more: read it, since what was said is where a secret most often is

    Other baseline packs (--pack-id pii.no, pii.dk, pii.fi, pii.is, pii.de) and packs of your own in the guardrails plugin's pack format, oz0.guardrails.pack/v1 (--pack FILE, such as your diary numbers) add patterns. The patterns can miss things and find things that are not secret: they are suggestions.
  3. Decide each item. Read review.html, then:
    Terminal
    oz0-public-access review ./review list --pending
    oz0-public-access review ./review set c00001 c00004 --mask --ground "OSL 26 kap. 1 §"
    oz0-public-access review ./review set c00002 --keep              # an official's work e-mail, handed out
    oz0-public-access review ./review set --all-pending --kind free_text --read
    oz0-public-access review ./review add ./export --key 3f9a… --text "the neighbour's diagnosis" \
      --ground "OSL 25 kap. 1 §"                                     # what the rules missed, masked wherever it is
    

    add finds the text as a whole word, in any case, in values, keys and numbers: --text Per masks Per and PER, never the start of permission.
    A mask needs a ground in your own words (the plugin suggests none); read says you read a free-text item and there is nothing more to mask in it; keep hands the text out as it is.
  4. Approve. The approval names who approved and the decision's reference, such as its diary number:
    Terminal
    oz0-public-access approve ./review --by "Rut Registrator" --decision-ref "KS 2026/1042"
    

    It is refused while any item is pending. It binds the SHA-256 of every decision: change one afterwards, with the command or by hand, and the approval is void. It records a person's act; it is not a signature: whoever can write the review folder can approve it, so keep the folder where only the registrars can.
  5. Release. Write the copy you hand out:
    Terminal
    oz0-public-access release ./export --key 3f9a… --review ./review --out ./copy
    

    copy/records/ holds the requested records with each masked place replaced by [MASKED n]: a masked key is renamed [MASKED n], and a masked number becomes the string "[MASKED n]". copy/release.json lists each n with its record, its place in the copy (a JSON pointer, with masked keys already masked) and the ground, and says who approved it, when and under which reference. The masked text is in no file of the copy. Release refuses when:
    • the review is not approved, or changed after it was approved;
    • it was made on another export, or a record changed since (the export is verified again);
    • a masked text still appears anywhere in the copy: the same personnummer kept in another field or record, written another way (811218 9876 or 19811218-9876 for 811218-9876) or as a number, a name kept in a JSON key or broken over a line, a masked paragraph repeated in another step's output, or a masked text in a ground you typed.
      Every item you mask counts, also one inside a larger text you masked whole: a personnummer in a masked paragraph is still looked for in the other fields.
      The check reads the decoded texts (values, keys and numbers), so a text with line breaks or quotes is found too, and it matches whole words in any case: a masked Per is found in Pers and PER, not in permission. The refusal says where, never what; mask those places too, or keep the text everywhere.

    Release sends nothing anywhere. Handing out the copy, and the decision that goes with it, with its appeal information, are the authority's.

The review folder holds the records' text: keep it as you keep the records.

Archive: an E-ARK package

Terminal
oz0-public-access archive ./export --key 3f9a… --submitter "Exempel kommun" --submitter-code 212000-0000 \
  --creator "Socialnämnden" --reference-code "SE/EK/SN" --submission-agreement "LA 2026:1" --tar sip.tar
oz0-public-access check sip.tar

The package follows the Common Specification for Information Packages (CSIP) 2.2.0 and the SIP specification 2.2.0 of E-ARK, with PREMIS 3.0 preservation metadata:

PathWhat it holds
uuid-…/One root folder, named after the package's ID
METS.xmlThe package's METS: the SIP profile, the submitting agent, the archival creator, the submission agreement and reference code, the files with their sizes and SHA-256, and the structure
metadata/descriptive/dc.xmlA Dublin Core description
metadata/preservation/premis.xmlEvery file with its SHA-256, size and format; the export's signature check (the key's fingerprint, and whether you pinned it) and the package's creation as events
representations/rep1/METS.xml, representations/rep1/data/The export's files byte for byte, its manifests, signatures and keys included, so the archive keeps the proof
documentation/What the package holds, and with --release ./copy the copy's release.json
schemas/The export manifest's shape as a JSON Schema

--record PATH archives only some files of the export (with its manifests and keys); --out FOLDER writes the folder instead of a tar. Every package is checked before it is written. oz0 tenant export --verify representations/rep1/data --key … checks the archived export again with the core.

check reads any E-ARK package, a folder or a tar, offline, and fails on what the specifications say a package must do: one root folder; the package and representation METS documents (their ID, content category, profile, header, software agent, the submitting agent, metadata references, file groups for documentation, schemas and representations, each file's size and checksum, its location inside the package, the CSIP structural map and its pointers); and each PREMIS file object's fixity. What a package should do is a warning.

It reads XML only without a DOCTYPE, so no entity is expanded, and fetches nothing: no schema, no outside validator. It reads and hashes each file once, however many references name it, and lists at most the first 1,000 findings. It does not check an authority's own profile (such as the National Archives' FGS packages) or what an archive must hold: ask your e-archive.

In the cluster

The tool oz0_public_access has the same steps as functions. Each takes the export as a file between tools (an export's tar, at most 32 MiB) and returns what it makes as a file of its own, so the records' text never reaches a model or a job's history: results carry counts, paths, item IDs and rule names only.

FunctionWhat it does
oz0_public_access.rulesThe masking rules in effect: the packs and their patterns, and how many names the list holds (never the names)
oz0_public_access.verifyexport, key: the export's checks, as verify
oz0_public_access.proposeexport, key, records, jobs: the review as a file, every item pending
oz0_public_access.archiveexport, key, submitter, creator, reference_code, submission_agreement: the SIP as a tar file
oz0_public_access.check_archivepackage: the check's errors and warnings

There is no release function in the cluster. People cannot fetch a file from the cluster or hand one back yet, so a review that reached a release call in a flow would have been decided and approved by a tool, and a person asked to approve that call would see only file hashes, never one decision. The masked copy is therefore written only by the command, from a review a person decided and approved on their own machine. Release joins these functions once a person can fetch the proposed review and hand the decided one back.

Files between tools are kept on the edge for 90 days after a tree last stored them. New files are sealed with the tenant's payload key where payload encryption is on, as it is by default in a cluster. Files stored before file scoping was introduced remain plain until rewritten.

A review file holds the records' text. Use the command when those records must stay off the cluster. These functions serve a flow whose earlier step produced the export as a file; the walkthrough above needs none of it.

A tar given to them may unpack to at most 1 GiB, and the command's to 64 times the tar's size (at least 256 MiB); for a larger export, unpack it and give the folder.

SettingWhat it is for
PUBLIC_ACCESS_NAMES (a secret)The names to look for, one per line: people whose names are themselves to be protected, so a secret
PUBLIC_ACCESS_BASELINEThe baseline packs, separated by commas; empty: pii.core and pii.se
PUBLIC_ACCESS_PACKSMore packs, as the guardrails plugin's GUARDRAILS_PACKS

The command

CommandWhat it does
verify EXPORT --key FP [--files] [--json]Check an export's signatures and every file's hash
rules [--pack-id ID] [--pack FILE] [--names FILE] [--json]List the masking rules in effect
propose EXPORT --key FP (--job ID | --record PATH | --table NAME)... --out REVIEWPropose what to mask: a review, every item pending
review REVIEW list [--pending] [--json]List the items
review REVIEW set ID... (--mask --ground G | --keep | --read | --pending) [--all-pending [--kind K]] [--note N]Decide items
review REVIEW add EXPORT --key FP --text T --ground G [--record PATH] [--pointer P]Mask text the rules missed, wherever it is
approve REVIEW --by NAME --decision-ref REFApprove every decision
release EXPORT --key FP --review REVIEW --out COPYWrite the masked copy
archive EXPORT --key FP --submitter ORG (--out FOLDER | --tar FILE) [...]Build an E-ARK SIP
check PACKAGE [--json]Check an E-ARK package

--trust-export-key instead of --key trusts the key the export carries, and every result says so. Exit status: 0 done or passed, 1 refused or failed (the reason on standard error), 2 used wrongly.

Tests

The plugin has no agent and no flow, so it has no evals to replay and no quality gate to pass: nothing in it calls a model. make test-plugins runs its tests (signatures and tampering, the rules, the review and its refusals, the E-ARK rules one by one, the MCP functions over files), and make e2e-public-access runs the walkthrough on a dev server's real export: verified with the pinned key, refused when changed, masked by a person's decisions, archived and checked, and the plugin's server answering on a node.

Copyright © 2026