Evidence pack
Halcyon Payments · Q3 2026 · strength E2
This pack is what PRGuard holds about the changes that reached a default branch in the period: which were audited before they merged, which were not and why, what the audits found and what people decided about it. It is evidence for the controls listed below. Whether a control is met is the assessor's judgement, and nothing in this pack says that one is.
What it covers
| Period | Q3 2026: 1 Jul 2026 to 30 Sep 2026, on the organisation's reporting clock (Europe/London). Exactly: from 2026-06-30T23:00:00Z up to and not including 2026-09-30T23:00:00Z, in UTC. |
|---|---|
| Frameworks | Cyber Resilience Act, EU AI Act |
| Control catalogue | 2026-10-17.1 |
| Repositories in the pack | How PRGuard reviews it | Default branch |
|---|---|---|
| merchant-sdk | Full review: a failed audit blocks the merge | main |
| risk-engine | Full review: a failed audit blocks the merge | main |
| Left out | Why |
|---|---|
| infra | Not a product with digital elements placed on the EU market. Not part of a high-risk AI system. The credit decision is made in risk-engine. |
| marketing-site | Static marketing site: no customer data and no production access. |
| payments-service | Not a product with digital elements placed on the EU market. Not part of a high-risk AI system. The credit decision is made in risk-engine. |
A repository left out is in no count, no list and no sample in this pack.
Coverage
171 of 180 changes were audited before they merged: 95%. 18 are gaps.
| Outcome | Changes |
|---|---|
| Passed | 162 |
| Merged anyway | 9 |
| Bypassed | 3 |
| Not audited | 6 |
| Not yet known | 0 |
Every change is a row of coverage.csv.
Who or what wrote the changes
| Origin | Changes | Audited before merge | Share |
|---|---|---|---|
| Human | 88 | 84 | 95.5% |
| AI-assisted | 46 | 44 | 95.7% |
| AI agent | 21 | 20 | 95.2% |
| Bot or automation | 25 | 23 | 92% |
Origin is classified from observable signals, such as a commit trailer or a bot's login. It is not ground truth. Each change an AI tool or a bot had a hand in is a row of ai-register.csv.
Controls
| Framework | Covered | Partly covered | Gaps | Not applicable | Share covered |
|---|---|---|---|---|---|
| Cyber Resilience Act | 4 | 0 | 0 | 0 | 100% |
| EU AI Act | 3 | 0 | 1 | 0 | 75% |
Covered: a rule file is mapped to the control and audits in the period ran with it. Partly covered: a rule file is mapped and no audit ran with it. Gap: nothing is mapped. Not applicable: the organisation said so, with a reason. Each control is a row of controls.csv; the mapped ones are set out in controls.md.
Exceptions
| Status when the pack was made | Exceptions |
|---|---|
| Expired | 1 |
| In force | 2 |
| Waiting for approval | 1 |
Every exception that touched the period, with who asked, who decided and when it ends, is a row of exceptions.csv.
Policies as the period ended
| Ignore skip markers | On |
|---|---|
| Minimum score to merge | Off |
| An error needs an exception to be closed as won’t fix | On |
| PR-only fix delivery | On |
The changes made during the period are in policies.json.
Sign-offs
I, Olivia Bennett, have reviewed the coverage ledger and the exceptions register of Halcyon Payments for Q3 2026 (1 Jul 2026 to 30 Sep 2026), for Cyber Resilience Act, EU AI Act, as PRGuard held them when I signed.
Signed 2026-10-05T09:12:00Z (UTC). Hash of what was held then: a9d51c54e9f67d57aa35333b21c13df7fa8dfc16458edd0a983b1f5f22a67356
A sign-off records that the period was reviewed. It is not a statement that the period was clean.
What is in the pack
manifest.json | Every file with its SHA-256, and the root hash over them. |
|---|---|
signature.json | PRGuard’s signature over manifest.json, and the key it was made with. |
scope.json | The scope statement above, for a program to read. |
coverage.csv | Every change that reached a default branch, or a branch a repository releases from: 180. |
releases.csv | Each tag pushed in the period, and the branch it was cut from: counted, not counted, or not known and why. |
controls.csv, controls.md | Each control's state and counts; the rules, repositories and policies behind each mapped control. |
rules/ | The full text of each rule file version that carried a control: 5. These are the organisation's own rules. |
samples/ | 82 sampled audits, up to 25 for each control, evenly spaced through the period. samples/index.csv says which belong to which control. Names, sizes, fingerprints and links: never source code. |
exceptions.csv | The exceptions register for the period. |
ai-register.csv | Changes an AI tool or a bot had a hand in, with who approved and merged each. |
policies.json | The organisation's policies and their changes. |
journal.jsonl | The append-only journal for the period: 620 entries, each with its own hash and linked to the entry written before it. 1041 lines carry an entry by its hashes only, written outside the period, about a repository the pack leaves out or naming a framework not offered, so the chain has no gap. |
attestations.json | The owner's sign-offs of this period. |
verify.py | The verifier. |
The columns of coverage.csv
| Column | What it says |
|---|---|
merged_at_utc | When the change landed on the branch, in UTC. |
repository | The repository it landed in. |
branch | The branch it landed on: the default branch, or a release branch the repository names. |
pull_request | The pull request's number. Empty for a direct push. |
merge_commit | The commit that landed on the branch. |
merged_commit | The pull request's last commit, the one that was merged. |
author | The GitHub login that opened the pull request or pushed the change. |
author_type | What GitHub says the author is: User or Bot. |
origin | Who or what wrote the change, as PRGuard classified it from what it could observe: HUMAN, AI_ASSISTED, AI_AGENT or DEPENDENCY_BOT (dependency bots and other automation). |
origin_agent | The tool PRGuard recognised, where it recognised one. |
origin_confidence | How sure the classification is: high, medium or low. |
origin_signal | What the classification rests on, such as a commit trailer or a bot's login. |
merged_by | The GitHub login that merged it. |
merged_by_its_author | yes when a person merged their own pull request. |
approved_by | The people, not bots, whose approval on GitHub stood when it merged. |
reviews | Whether PRGuard read the pull request's reviews from GitHub: read, not read yet, could not be read, or no pull request. It says the reviews were read, not that anyone approved. |
outcome_class | The outcome's key: what PRGuard did about the change before it merged. |
outcome | The outcome in words. |
reason | Why, where the outcome needs a reason, such as why a change was not audited. |
verdict | The deciding audit's verdict: PASS, WARN or FAIL. Empty when no audit decided it. |
score | The deciding audit's score out of 100. |
audits | How many audits PRGuard ran on the change. |
review_mode | How PRGuard reviewed the repository that day: FULL_REVIEW, where a failed audit asks GitHub to block the merge, or COMMENT_ONLY. |
prguard_review_event | The review PRGuard itself posted on the pull request for the deciding audit: APPROVE, REQUEST_CHANGES or COMMENT; empty where none was posted. It is PRGuard's own review, never a person's approval: people's approvals are in approved_by. |
forced | yes when the change arrived by a force-push. |
source | How PRGuard learned of it: pr_closed (a merged pull request), push (a push to the branch) or backfill (read from GitHub's history). |
record_id | PRGuard's id for this record of the change. |
audit_id | PRGuard's id for the deciding audit. Empty when no audit decided it. |
A change PRGuard did not audit has no verdict, score or audit id. Times are UTC.
How to check it
- Unzip the pack, or leave it zipped.
- Run: python verify.py <pack.zip or folder> --expect <the root hash PRGuard sent you> --key <PRGuard's public key>
- VERIFIED means every listed file matches its hash, nothing is missing or added, the root hash is the one expected, a signed pack's signature is PRGuard's, and every journal line links to the one before.
- PRGuard publishes every key it has signed packs with at /.well-known/prguard-pack-key.json on its site, each with its id, its public key, its dates and whether it was revoked; the Packs tab and the email name the one used. Pass its public key; its id alone names only part of it.
- verify.py needs Python 3.9 or later and nothing else. It reads the pack and writes nothing. prguard verify does the same check.
E2. Attested: E1, plus PRGuard's signature over the manifest, checked against its published key, and a journal whose entries are chained, so the pack shows who made it and that no entry between its first and last line was removed after it was written; neither shows the records were right.
The records are append-only: PRGuard does not edit or delete them. That is not the same as tamper-proof. The signature shows PRGuard made this manifest, when it is checked against PRGuard’s published key; the chain shows no entry between the journal’s first and last line was removed after it was written. Neither shows the records were right.
The records this pack was made from are retained while the organisation's PRGuard account exists.
prguard-evidence-pack/2