---
title: "ai.oz0/public-access"
description: "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."
canonical_url: "https://docs.oz0.ai/build/plugins/verified/public-access"
---
# 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](https://docs.oz0.ai/admin/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.

<note>

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.

</note>

## Install the command

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

```bash [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](https://docs.oz0.ai/build/plugins/verified/public-access#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:```bash [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
```

<br />

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:```bash [Terminal]
oz0-public-access propose ./export --key 3f9a… --job job-123 --job job-456 \
  --table job_board --names ./names.txt --out ./review
```

<br />

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:<table>
<thead>
  <tr>
    <th>
      Candidate
    </th>
    
    <th>
      Found by
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        personnummer
      </code>
      
      , <code>
        samordningsnummer
      </code>
    </td>
    
    <td>
      Swedish personal identity and coordination numbers, with their date and control digit
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        email
      </code>
      
      , <code>
        phone
      </code>
      
      , <code>
        iban
      </code>
      
      , <code>
        payment card
      </code>
    </td>
    
    <td>
      The guardrails plugin's baseline personal-data patterns
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        name
      </code>
    </td>
    
    <td>
      Each name of the list you give with <code>
        --names
      </code>
      
       (one per line), whole words or in the genitive (<code>
        Annas
      </code>
      
      ), any case
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        free text
      </code>
    </td>
    
    <td>
      A 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
    </td>
  </tr>
</tbody>
</table>

<br />

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:```bash [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
```

<br />

`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`.<br />

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:```bash [Terminal]
oz0-public-access approve ./review --by "Rut Registrator" --decision-ref "KS 2026/1042"
```

<br />

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:```bash [Terminal]
oz0-public-access release ./export --key 3f9a… --review ./review --out ./copy
```

<br />

`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.<br />
  
  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.<br />
  
  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.<br />

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

```bash [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:

<table>
<thead>
  <tr>
    <th>
      Path
    </th>
    
    <th>
      What it holds
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        uuid-…/
      </code>
    </td>
    
    <td>
      One root folder, named after the package's ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        METS.xml
      </code>
    </td>
    
    <td>
      The 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
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        metadata/descriptive/dc.xml
      </code>
    </td>
    
    <td>
      A Dublin Core description
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        metadata/preservation/premis.xml
      </code>
    </td>
    
    <td>
      Every 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
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        representations/rep1/METS.xml
      </code>
      
      , <code>
        representations/rep1/data/
      </code>
    </td>
    
    <td>
      The export's files byte for byte, its manifests, signatures and keys included, so the archive keeps the proof
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        documentation/
      </code>
    </td>
    
    <td>
      What the package holds, and with <code>
        --release ./copy
      </code>
      
       the copy's <code>
        release.json
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        schemas/
      </code>
    </td>
    
    <td>
      The export manifest's shape as a JSON Schema
    </td>
  </tr>
</tbody>
</table>

`--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](https://docs.oz0.ai/reference/plugin-manifest/tools#files-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.

<table>
<thead>
  <tr>
    <th>
      Function
    </th>
    
    <th>
      What it does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        oz0_public_access.rules
      </code>
    </td>
    
    <td>
      The masking rules in effect: the packs and their patterns, and how many names the list holds (never the names)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        oz0_public_access.verify
      </code>
    </td>
    
    <td>
      <code>
        export
      </code>
      
      , <code>
        key
      </code>
      
      : the export's checks, as <code>
        verify
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        oz0_public_access.propose
      </code>
    </td>
    
    <td>
      <code>
        export
      </code>
      
      , <code>
        key
      </code>
      
      , <code>
        records
      </code>
      
      , <code>
        jobs
      </code>
      
      : the review as a file, every item pending
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        oz0_public_access.archive
      </code>
    </td>
    
    <td>
      <code>
        export
      </code>
      
      , <code>
        key
      </code>
      
      , <code>
        submitter
      </code>
      
      , <code>
        creator
      </code>
      
      , <code>
        reference_code
      </code>
      
      , <code>
        submission_agreement
      </code>
      
      : the SIP as a tar file
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        oz0_public_access.check_archive
      </code>
    </td>
    
    <td>
      <code>
        package
      </code>
      
      : the check's errors and warnings
    </td>
  </tr>
</tbody>
</table>

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](https://docs.oz0.ai/build/parts/tools/results-and-files#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.

<table>
<thead>
  <tr>
    <th>
      Setting
    </th>
    
    <th>
      What it is for
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        PUBLIC_ACCESS_NAMES
      </code>
      
       (a secret)
    </td>
    
    <td>
      The names to look for, one per line: people whose names are themselves to be protected, so a secret
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PUBLIC_ACCESS_BASELINE
      </code>
    </td>
    
    <td>
      The baseline packs, separated by commas; empty: <code>
        pii.core
      </code>
      
       and <code>
        pii.se
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PUBLIC_ACCESS_PACKS
      </code>
    </td>
    
    <td>
      More packs, as the guardrails plugin's <code>
        GUARDRAILS_PACKS
      </code>
    </td>
  </tr>
</tbody>
</table>

## The command

<table>
<thead>
  <tr>
    <th>
      Command
    </th>
    
    <th>
      What it does
    </th>
  </tr>
</thead>

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

`--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.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
