Публічний реєстр
Звіт про здоров'я програмного забезпеченнясхема 0.26.0 · метрики 1.13.0 · 2026-07-22 02:15 UTC

encryption4all / postguard-js

TypeScriptЛіцензію не виявлено★ 0 зірок⑂ 0 форківз бер. 2026 р.Переглянути на GitHub ↗

encryption4all/postguard-js має індекс здоров’я 57 зі 100, що відповідає смузі «Помірний». Найвищий показник — Vitality (85/100), найнижчий — Community & Adoption (24/100). Останнє оновлення — сьогодні. Більшість нещодавньої роботи виконує один учасник.

57
загалом / 100
Помірний

Індекс здоров'я програмного забезпечення

Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє середнє; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 49 («Під ризиком»). Готовність до ШІ не входить до індексу.

57
Відмінний85-100Зразковий; відповідає практично всім перевіреним критеріям
Добрий70-84Здоровий; незначні прогалини
Помірний50-69Прийнятний, але з помітними прогалинами; рекомендовано перевірку
У зоні ризику30-49Суттєві слабкі місця; впровадження потребує обережності
Критичний1-29Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
ЖиттєздатністьСпільнота тавпровадженняСталість таврядуванняІнженернаякістьБезпекаГотовність доШІ

Профіль оцінок

Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.

Власність

Encryption for allОрганізація
6 підписників16 публічних репозиторіївз лют. 2021 р.

За цим репозиторієм стоїть організація — спільна, підзвітна опіка, здатна пережити будь-якого окремого мейнтейнера.

Пакетні екосистеми

РеєстрПакетВерсіяЗавантажень / місВерсіїОстання публікаціяТеги
npm@e4a/pg-js2.3.14 063514 дні томуpostguardibeencryptionidentity-based-encryptionyiviirma

Метрики за категоріями

Життєздатність

Чи живий проєкт — чи пишеться код і чи виходять релізи?

85Відмінний · 22% загального індексу
Як обчислюється оцінка
36/36Свіжість push — останній push 0 дн. тому
11.1/36Ритм комітів — 16/52 тижнів із комітами
18/18Обсяг комітів — 210 комітів за останній рік
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Використані вхідні дані
commits_last_year210
human_commit_share0,74
days_since_last_push0
active_weeks_last_year16
Як обчислюється оцінка
27/27Випускає релізи — опубліковано 49 релізів
36/36Свіжість релізів — останній реліз 4 дн. тому
27/27Ритм релізів — реліз кожні ~1,6 дн.
0/10OpenSSF Scorecard: Signed-Releases — немає даних
Використані вхідні дані
releases_count49
latest_release_tagv2.3.1
releases_from_tagsні
days_since_latest_release4
mean_days_between_releases1,6
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Signed-Releases. Залишкові ваги перенормовано.

Спільнота та впровадження

Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?

24Критичний · 18% загального індексу
Як обчислюється оцінка
0/60Зірки — 0 зірок
0/25Форки — 0 форків
0/15Спостерігачі — 0 спостерігачів
Використані вхідні дані
forks0
stars0
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Як обчислюється оцінка
22.5/22.5README
0/22.5Ліцензія — файлу ліцензії не виявлено
0/18Настанови CONTRIBUTING
0/13.5Кодекс поведінки
0/7.2Шаблон issue
0/6.3Шаблон PR
Використані вхідні дані
has_readmeтак
has_licenseні
has_contributingні
has_issue_templateні
has_code_of_conductні
has_pull_request_templateні
Як обчислюється оцінка
48.1/80Щомісячні завантаження — 4 063 завантажень/місяць у npm
0/20Залежні пакети в реєстрі — ця екосистема цього не повідомляє
Використані вхідні дані
packages@e4a/pg-js
dependents
ecosystemsnpm
total_downloads
monthly_downloads4 063
Виключено з оцінювання (немає даних або не застосовно): Залежні пакети в реєстрі. Залишкові ваги перенормовано.

Сталість та врядування

Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?

60Помірний · 24% загального індексу
Як обчислюється оцінка
9/54Бас-фактор — на 1 контриб’ютор(ів) припадає половина всіх комітів
0/22.5Розподіл комітів — головний контриб’ютор — автор 100% комітів
1.4/13.5Широта контриб’юторів — 1 контриб’юторів
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Використані вхідні дані
bus_factor1
contributors_sampled1
top_contributor_share1
Як обчислюється оцінка
41.9/46.8Вирішення issue — закрито 90% issue
37.7/38.3Прийняття PR — злито 75/76 вирішених PR
12/15OpenSSF Scorecard: Code-Review — Found 4/5 approved changesets -- score normalized to 8
Використані вхідні дані
merged_prs75
open_issues4
closed_issues35
issue_closed_ratio0,897
closed_unmerged_prs1
Як обчислюється оцінка
30/30Підтримка власника — у власності організації
0/20Верифікований домен
6.1/25Охоплення власника — 6 підписників у encryption4all
19.9/25Послужний список — 16 публічних репозиторіїв, вік облікового запису ~5 р.
Використані вхідні дані
followers6
owner_typeOrganization
is_verified
owner_loginencryption4all
public_repos16
account_age_days1 992

Супровід пакетів

100Відмінний
Як обчислюється оцінка
25/25Опубліковано й доступно — 1 пакет(ів) у npm
35/35Свіжість публікацій — остання публікація 4 дн. тому
20/20Історія версій — 51 опублікованих версій
20/20Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані
packages@e4a/pg-js
ecosystemsnpm
any_deprecatedні
min_days_since_publish4

Інженерна якість

Чи наявні базові інженерні практики та документація?

57Помірний · 20% загального індексу
Як обчислюється оцінка
24/24Процеси CI — 3 процес(ів) CI
24/24Наявні тести
0/16Конфігурація лінтера
0/9.6Pre-commit-хуки
0/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
Використані вхідні дані
has_ciтак
has_testsтак
has_editorconfigні
has_linter_configні
has_precommit_configні

Документація

40У зоні ризику
Як обчислюється оцінка
30/30README
0/25Каталог документації
0/15Сайт документації / домашня сторінка
0/10Опис репозиторію
0/10Теми
10/10Wiki
Використані вхідні дані
topics
has_wikiтак
homepage
has_readmeтак
has_docs_dirні
has_descriptionні

Безпека

Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?

53Помірний · 16% загального індексу

Стан безпеки

53Помірний
Як обчислюється оцінка
7.5/7.5Binary-Artifacts — no binaries found in the repo
3.8/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
6/7.5Code-Review — Found 4/5 approved changesets -- score normalized to 8
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
0/2.5Ліцензія — license file not detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
2.5/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 5
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — немає даних
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
6.8/7.5Vulnerabilities — 1 existing vulnerabilities detected
Використані вхідні дані
sourceopenssf_scorecard
checks_evaluated17
scorecard_versionv5.5.0
checks_inconclusive1
scorecard_aggregate5,3
Виключено з оцінювання (немає даних або не застосовно): signed_releases. Залишкові ваги перенормовано.

Готовність до ШІ

Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Незалежний, експериментальний бейдж — вага 0.0, тож він подається окремо і не впливає на загальний індекс здоров'я.

75Добрий · 0% загального індексу
Як обчислюється оцінка
45/45Інструкції для агентів — CLAUDE.md
0/15Машиночитана документація (llms.txt)
40/40Читабельна історія комітів — намір зазначено у 74 з 74 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
has_llms_txtні
legible_history_share1
agent_instruction_filesCLAUDE.md
agent_instruction_max_bytes10 266
Як обчислюється оцінка
0/18Розгортання однією командою
22/22Автоматизовані тести
0/11Конфігурація лінтера / форматера
11/11Статична перевірка типів — tsconfig.json
10/10Відтворюване середовище — lockfile
10/10Підтверджена практика роботи з агентами — 13 з останніх 100 комітів створено агентом або з його зазначенням
0/8Автоматизоване супроводження — автоматичних оновлень залежностей не виявлено
5/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 5
Використані вхідні дані
has_nixні
has_testsтак
lockfilespackage-lock.json
has_dockerfileні
typed_languageтак
bootstrap_files
has_devcontainerні
has_linter_configні
typecheck_configstsconfig.json
agent_commit_share0,13
toolchain_manifests
dependency_bot_commit_share0
Як обчислюється оцінка
45/45Типізований код — TypeScript (статично типізована)
55/55Керовані розміри файлів — 0/60 файлів вихідного коду понад 60 КБ
Використані вхідні дані
primary_languageTypeScript
largest_source_bytes41 642
source_files_sampled60
oversized_source_files0

Ключові факти

0зірок GitHub
1контриб'юторів
210комітів за останні 12 місяців
0днів від останнього пушу
49релізів
1бас-фактор
4відкритих issue
npmпакетних екосистем

Попередження щодо збору даних

  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository
  • deps.dev does not index npm:@e4a/pg-js@2.3.1; advisories assessed against the repository dependency graph instead

Докладніше

OpenSSF Scorecard 5.3 / 10
5.3сукупно

Незалежна, не прив'язана до інструментів оцінка безпеки від відкритого проєкту OpenSSF Scorecard. Кожна перевірка винагороджує практику безпеки, а не інструмент конкретного постачальника. Перевірки, які Scorecard не зміг визначити, позначено н/д і виключено з оцінки безпеки (вони ніколи не зараховуються як нуль).Scorecard v5.5.0 · 2026-07-22 02:15 UTC

10Binary-Artifactsno binaries found in the repo
5Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
8Code-ReviewFound 4/5 approved changesets -- score normalized to 8
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
0Licenselicense file not detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
5Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 5
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
н/дSigned-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
9Vulnerabilities1 existing vulnerabilities detected
Прямі залежності 6
РеєстрПакетОбмеження версіїМаніфест
npm@e4a/pg-wasm^0.6.1package.json
npm@privacybydesign/yivi-client^1.0.0package.json
npm@privacybydesign/yivi-core^1.0.0package.json
npm@privacybydesign/yivi-css^1.0.1package.json
npm@privacybydesign/yivi-web^1.0.1package.json
npm@transcend-io/conflux^6.1.3package.json
Усі залежності не зібрано

Не вдалося зібрати розв'язаний набір залежностей для цього звіту: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Звіт у форматі JSON машиночитний
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 512,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "JavaScript": 11281,
        "TypeScript": 283658
      },
      "pushed_at": "2026-07-21T14:56:52Z",
      "created_at": "2026-03-25T12:12:25Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-21T15:00:34Z",
      "description": null,
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": null,
      "default_branch": "main",
      "license_spdx_raw": null,
      "primary_language": "TypeScript",
      "significant_languages": [
        "TypeScript"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Encryption for all",
      "type": "Organization",
      "login": "encryption4all",
      "company": null,
      "location": null,
      "followers": 6,
      "avatar_url": "https://avatars.githubusercontent.com/u/78606495?v=4",
      "created_at": "2021-02-05T15:03:12Z",
      "is_verified": null,
      "public_repos": 16,
      "account_age_days": 1992
    },
    "license": {
      "state": "absent",
      "spdx_id": null,
      "raw_spdx": null,
      "file_present": false,
      "scorecard_found": false,
      "profile_has_license": false
    },
    "activity": {
      "releases": [
        {
          "tag": "v2.3.1",
          "kind": "patch",
          "published_at": "2026-07-17T05:57:23Z"
        },
        {
          "tag": "v2.3.0",
          "kind": "minor",
          "published_at": "2026-07-16T18:02:59Z"
        },
        {
          "tag": "v2.2.0",
          "kind": "minor",
          "published_at": "2026-07-15T07:34:21Z"
        },
        {
          "tag": "v2.1.7",
          "kind": "patch",
          "published_at": "2026-07-15T07:16:00Z"
        },
        {
          "tag": "v2.1.6",
          "kind": "patch",
          "published_at": "2026-07-15T07:12:38Z"
        },
        {
          "tag": "v2.1.5",
          "kind": "patch",
          "published_at": "2026-07-14T14:13:46Z"
        },
        {
          "tag": "v2.1.4",
          "kind": "patch",
          "published_at": "2026-07-06T13:44:27Z"
        },
        {
          "tag": "v2.1.3",
          "kind": "patch",
          "published_at": "2026-07-06T13:39:24Z"
        },
        {
          "tag": "v2.1.2",
          "kind": "patch",
          "published_at": "2026-07-02T11:37:42Z"
        },
        {
          "tag": "v2.1.1",
          "kind": "patch",
          "published_at": "2026-07-02T11:36:31Z"
        },
        {
          "tag": "v2.1.0",
          "kind": "minor",
          "published_at": "2026-06-19T12:09:24Z"
        },
        {
          "tag": "v2.0.2",
          "kind": "patch",
          "published_at": "2026-06-08T12:49:02Z"
        },
        {
          "tag": "v2.0.1",
          "kind": "patch",
          "published_at": "2026-06-03T09:49:36Z"
        },
        {
          "tag": "v2.0.0",
          "kind": "major",
          "published_at": "2026-06-02T10:52:46Z"
        },
        {
          "tag": "v1.11.0",
          "kind": "minor",
          "published_at": "2026-06-02T07:46:35Z"
        },
        {
          "tag": "v1.10.3",
          "kind": "patch",
          "published_at": "2026-06-02T07:36:39Z"
        },
        {
          "tag": "v1.10.2",
          "kind": "patch",
          "published_at": "2026-06-02T07:35:28Z"
        },
        {
          "tag": "v1.10.1",
          "kind": "patch",
          "published_at": "2026-06-02T07:34:23Z"
        },
        {
          "tag": "v1.10.0",
          "kind": "minor",
          "published_at": "2026-05-24T13:45:10Z"
        },
        {
          "tag": "v1.9.0",
          "kind": "minor",
          "published_at": "2026-05-24T13:33:37Z"
        },
        {
          "tag": "v1.8.0",
          "kind": "minor",
          "published_at": "2026-05-16T12:49:11Z"
        },
        {
          "tag": "v1.7.1",
          "kind": "patch",
          "published_at": "2026-05-13T16:30:29Z"
        },
        {
          "tag": "v1.7.0",
          "kind": "minor",
          "published_at": "2026-05-13T15:43:25Z"
        },
        {
          "tag": "v1.6.2",
          "kind": "patch",
          "published_at": "2026-05-12T07:24:14Z"
        },
        {
          "tag": "v1.6.1",
          "kind": "patch",
          "published_at": "2026-05-12T07:21:56Z"
        },
        {
          "tag": "v1.6.0",
          "kind": "minor",
          "published_at": "2026-05-07T20:45:17Z"
        },
        {
          "tag": "v1.5.0",
          "kind": "minor",
          "published_at": "2026-05-07T19:01:42Z"
        },
        {
          "tag": "v1.4.0",
          "kind": "minor",
          "published_at": "2026-05-07T12:12:49Z"
        },
        {
          "tag": "v1.3.0",
          "kind": "minor",
          "published_at": "2026-05-06T11:22:20Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2026-05-02T10:24:25Z"
        },
        {
          "tag": "v1.1.0",
          "kind": "minor",
          "published_at": "2026-05-01T11:48:35Z"
        },
        {
          "tag": "v1.0.3",
          "kind": "patch",
          "published_at": "2026-05-01T11:32:38Z"
        },
        {
          "tag": "v1.0.2",
          "kind": "patch",
          "published_at": "2026-05-01T11:31:34Z"
        },
        {
          "tag": "v1.0.1",
          "kind": "patch",
          "published_at": "2026-05-01T11:30:26Z"
        },
        {
          "tag": "v0.10.0",
          "kind": "minor",
          "published_at": "2026-04-24T11:12:09Z"
        },
        {
          "tag": "v0.9.3",
          "kind": "patch",
          "published_at": "2026-04-21T12:27:53Z"
        },
        {
          "tag": "v0.9.2",
          "kind": "patch",
          "published_at": "2026-04-21T09:02:01Z"
        },
        {
          "tag": "v0.9.1",
          "kind": "patch",
          "published_at": "2026-04-16T20:18:48Z"
        },
        {
          "tag": "v0.9.0",
          "kind": "minor",
          "published_at": "2026-04-16T20:03:15Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2026-04-16T19:46:25Z"
        },
        {
          "tag": "v0.7.2",
          "kind": "patch",
          "published_at": "2026-04-16T19:20:50Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2026-04-14T13:55:31Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-04-10T14:41:20Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-04-10T13:28:35Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-04-09T14:21:55Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-04-09T08:59:25Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-04-09T07:47:03Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-03-30T08:30:09Z"
        },
        {
          "tag": "v0.1.3",
          "kind": "patch",
          "published_at": "2026-03-25T15:21:15Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "6c0f55443706ba27fda1db73f48655795cb151d9",
          "body": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
          "is_bot": false,
          "headline": "Merge pull request #117 from encryption4all/test/encrypt-pipeline",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-21T14:56:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9e821471ad440126cdb2c5f37331b41047343659",
          "body": "The encrypt/upload pipeline in src/crypto/encrypt.ts had no direct test\ncoverage. Add unit tests with sealStream/loadWasm and the Cryptify upload\nsink mocked:\n\n- awaitAllOrAbort: happy path (no abort), first-failure abort + re-throw\n  of the first error from either side, loser-rejection is swallowed\n[…]\nciphertext.\n\nawaitAllOrAbort is exported for direct testing; it is not re-exported from\nsrc/index.ts, so the package's public API is unchanged.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-17T17:09:23Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c7d21aae03cadcf4fbf8e09a7b6beed73d3d6e4f",
          "body": "fix: floor the seal policy timestamp so it is never in the future",
          "is_bot": false,
          "headline": "Merge pull request #116 from encryption4all/fix/seal-timestamp-floor",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-17T05:56:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f3169a6004a67b1d7bb749e083f18052e7155463",
          "body": "The PKG rejects a USK request whose timestamp is > now (\"chronology error\").\nThe seal timestamp was stamped with Math.round(Date.now()/1000), which can\nland up to ~1s ahead of the real time, so an encrypt→decrypt within the same\nsecond could fail. In normal usage the multi-second gap masks it; the e\n[…]\ne\nharness's headless flow (encryptionall/postguard-e2e) completes in <1s and\nsurfaced it.\n\nExtract nowSeconds() (floor) and use it for both seal timestamps. Adds unit\ntests pinning the floor behavior.",
          "is_bot": false,
          "headline": "fix: floor the seal policy timestamp so it is never in the future",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-17T05:50:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "854d5c06ea7c852e8ebb8517687d71f0349bfd51",
          "body": "feat: configurable email attribute types (emailAttributes option)",
          "is_bot": false,
          "headline": "Merge pull request #115 from encryption4all/feat/email-attribute",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T18:01:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7b1c6287bd8a0b56e53b096c56262603c7918e91",
          "body": "Review catch: the apiKey dispatch case dropped the configured attributes and\nfetchSigningKeysWithApiKey hardcoded the production type in its request body\n- under an override, an api-key sender would request the production type\nwhile recipient policies used the overridden one. Threaded through\n(dispa\n[…]\nst capturing the request body. Note: the PKG derives the\napi-key signing identity from the business database and ignores this body\ntoday; the configured type is sent anyway for wire-level consistency.",
          "is_bot": false,
          "headline": "fix: thread emailAttributes through the api-key signing path too",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T18:01:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b41fe228daf90c6a7bf831b6a63251fe19049642",
          "body": "Part 3 of encryption4all/postguard#236 (the pg-js leg). The pbdf email and\nemail-domain attribute types were hardcoded across recipient builders, key\nrequests, decryption hints, includeSender policies and all signing flows —\nmaking it impossible to run the real SDK in any test environment (pbdf\ncred\n[…]\ns are unaffected — all 212 existing tests pass untouched; 6\nnew tests pin the override and default behavior.\n\nCompanions: encryption4all/postguard#244 (PKG) and\nencryption4all/cryptify#193 (cryptify).",
          "is_bot": false,
          "headline": "feat: configurable email attribute types (emailAttributes option)",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T17:52:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f8bd16b154b79b92bb086c2c088f10e79babb7db",
          "body": "feat(sign): prepareSign() — pre-warm a Yivi session for one-tap app open on iOS",
          "is_bot": false,
          "headline": "Merge pull request #103 from encryption4all/feat/prepare-sign-prewarm",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:33:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c4a26c324fc32ae69543043be92a9379f5dee34f",
          "body": "docs: extend CLAUDE.md with migrated agent notes",
          "is_bot": false,
          "headline": "Merge pull request #104 from encryption4all/chore/add-claude-md",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:21:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "572c91a5f98c4d9ab4db20fe1fb8ae4866537b20",
          "body": "perf(chunker): fill output buffers from chunk views to cut per-chunk copies",
          "is_bot": false,
          "headline": "Merge pull request #111 from encryption4all/perf/chunker-zero-copy",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:15:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6181086d3b8986a54e543411fb5b7bbfa2a68a73",
          "body": "fix(download): cancel previous reader before retrying",
          "is_bot": false,
          "headline": "Merge pull request #113 from encryption4all/fix/108-download-reader-leak",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:11:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c457be42a94d231c673a675cd6f7dc318d9c0294",
          "body": "The pre* hooks run three generators (wasm-base64, yivi-css, version),\nnot two; version.ts is itself a gitignored build-time source.",
          "is_bot": true,
          "headline": "docs: correct generator count in CLAUDE.md build-pipeline note",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-15T07:00:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d21ce6ffb51fa3fac00fd53895f61808726e973",
          "body": "The migrated 'Repo layout' note described integration.yml as running\n'typecheck, build, test on Node 24', which under-states the actual matrix\n(Node 22 and 24, plus Bun and Deno lanes) and every lane's smoke step. It\nalso contradicted the accurate description already present under 'Releases\nand CI'. Align the two.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "docs: correct integration.yml CI description in repo-layout note",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-15T06:55:46Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "6326ca81c7cb6c410e385205f54e60a3010af702",
          "body": "On the retrying download stream's `start()` source, each attempt opened a\nfresh fetch/reader but never released the previous one on a mid-stream\nerror. On a flapping connection every retry left a dangling response-body\nreader holding a lock on an abandoned stream.\n\nHoist `reader` so the `catch` can \n[…]\nping back to a fresh fetch. Add a\nfail-then-succeed regression test asserting the first attempt's reader is\ncancelled exactly once.\n\nFixes #108\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix(download): cancel previous reader before retrying",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-14T23:40:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b5de5f08a7e83e3475459c0db6c63540b15ba049",
          "body": "…copies\n\nChunker copied streamed data three times per emitted chunk: chunk.slice()\nallocated a copy of the slice, which was copied into a reused ArrayBuffer,\nwhich was then copied again into a fresh output buffer before enqueue. Over\nthe whole ciphertext stream on large uploads this is a large amoun\n[…]\nre unchanged.\n\nAdd tests covering data spanning a chunk boundary across multiple transform\ncalls and buffer independence across emitted chunks.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "perf(chunker): fill output buffers from chunk views to cut per-chunk …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-14T23:39:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "7dee2640ec73acf379b9f297ffc50dab8f330201",
          "body": null,
          "is_bot": false,
          "headline": "docs: add agent & contributor notes (migrated from dobby memory)",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T18:46:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e8a84f79d825926165c1c1adede03699e1bcd92f",
          "body": "…app open\n\nOn iOS a Yivi Universal Link only opens the app when the navigation happens\ninside a genuine user gesture. If the signing session is started on tap (as\npart of encrypt()), the app deep-link URL doesn't exist yet, so the tap can't\nnavigate synchronously and falls back to Safari.\n\nprepareSi\n[…]\ns` field; when supplied, Sealed.getSigningKeys() uses it\ndirectly and never starts a second session (the internal pipeline already\nthreaded signingKeys). Adds AbortSignal support to the Yivi resolver.",
          "is_bot": false,
          "headline": "feat(sign): add prepareSign() to pre-warm a Yivi session for one-tap …",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:38:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b2f55ad9948d3c163e68d97830a6d2fe945ddbb6",
          "body": "fix(yivi): use SSE for session status, fall back to polling",
          "is_bot": false,
          "headline": "Merge pull request #102 from encryption4all/fix/yivi-sse-status-updates",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:12:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f02725a1330b805d3d94b2b0bfaaf393007e0a36",
          "body": "The Yivi session state config hardcoded `serverSentEvents: false`, which\nforced yivi-client's StatusListener into polling for every disclosure/\nsigning session. Enable the irmaserver's Server-Sent Events stream\n(/frontend/statusevents) instead — the same default yivi-client ships —\nso status updates\n[…]\nck to\npolling when the SSE connection can't be established.\n\nApplies to both the signing (resolveSigningKeysFromYivi) and decrypt\n(retrieveUSKViaYivi) flows.\n\nRefs encryption4all/postguard-website#317",
          "is_bot": false,
          "headline": "fix(yivi): use SSE for session status, fall back to polling",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:07:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "54dced1bbb1c5c52946335efc4db7cfe4850ee25",
          "body": "fix: escape quotes in attachment names and sanitize injected header names",
          "is_bot": false,
          "headline": "Merge pull request #99 from encryption4all/fix/97-mime-name-escaping",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-10T08:36:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e044d7ac3b37bfa16b3545cdd12b164a36de963e",
          "body": "fix: harden client-side JWT handling in the Yivi session path",
          "is_bot": false,
          "headline": "Merge pull request #101 from encryption4all/fix/98-jwt-hardening",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-06T13:43:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2a4f2f5b8caf98a5a5c6e73465e7008144b1dc2f",
          "body": "…ars-mime-headers\n\nfix: strip control characters from MIME header values",
          "is_bot": false,
          "headline": "Merge pull request #100 from encryption4all/security/strip-control-ch…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-06T13:38:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d0e47238eb6abdc62a9d9a2835053caaf03567a1",
          "body": "Yivi/IRMA session-result JWTs are decoded client-side without signature\nverification, so their claims must not be trusted verbatim.\n\n- New shared util src/util/jwt.ts -> decodeJwtPayloadUnsafe: structural-only\n  decode (3 non-empty segments, base64url + UTF-8), returns null on any\n  malformation; do\n[…]\nt-provided senderEmail wins over the JWT value.\n\nDefense-in-depth: the PKG server verifies the JWT signature before issuing keys.\n\nResolves #98\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: harden client-side JWT handling in the Yivi session path",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:24:59Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b4171400a0e9ba3bf143b5e2ab3b6b1affcce927",
          "body": "The comment claimed a bare CR was embedded, but no CR byte is present in\nany field; list the actual control bytes (NUL, SOH, backspace, VT, ESC,\nDEL) instead.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "test: fix control-char test comment to match embedded bytes",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:20:49Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4c5618263fa2132164f42c4fa7ed57503231829e",
          "body": "sanitizeHeaderValue now removes C0 control characters and DEL from\nuser-supplied header values, in addition to collapsing CR/LF runs to a\nsingle space, so no control byte survives interpolation into the MIME\ntemplate. Tab is preserved as valid header whitespace.\n\nAdds regression tests for control-character stripping and tab\npreservation.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: strip control characters from MIME header values",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:10:19Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5becc13d72802e783f8f9aaabfd06755558e2d77",
          "body": "…ames\n\nTwo related MIME header-injection gaps in src/email/mime.ts (GHSA-qmf2-7wp2-gm9x):\n\n- injectMimeHeaders interpolated header names (and values) from headersToInject\n  without stripping CR/LF, allowing extra header lines to be smuggled in. Both\n  name and value are now run through sanitizeHeade\n[…]\nnject additional parameters. Added\n  escapeQuotedStringParam (backslash + quote escaping after CR/LF folding).\n\nAdds regression tests for both.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: escape quotes in attachment names and sanitize injected header n…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-04T23:06:54Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b5c4b19e632101935e273954173613355e80ff92",
          "body": "fix: sanitize user input in MIME construction",
          "is_bot": false,
          "headline": "Merge pull request #96 from encryption4all/fix/93-mime-sanitization",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T11:36:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7dc6253ddfcce2d81811aad18e7dd85d743521b9",
          "body": "fix(envelope): tighten validation of the websiteUrl option",
          "is_bot": false,
          "headline": "Merge pull request #95 from encryption4all/fix/94-validate-website-url",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T11:35:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "67b1e496197880a1e0760290116f1929e2eaf295",
          "body": "chore: update dependencies",
          "is_bot": false,
          "headline": "Merge pull request #92 from encryption4all/chore/update-dependencies",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T08:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2c884aa31e18743bf221b2f8ae830b127f04b0cc",
          "body": "Harden src/email/mime.ts against header injection:\n\n- Strip CR/LF runs from all user-supplied header values (from, to, cc,\n  subject, inReplyTo, references, attachment name/type) before they are\n  interpolated into the MIME template, collapsing them to a single space\n  so a crafted value cannot smug\n[…]\nme\n  containing metacharacters is matched literally rather than as a pattern.\n\nAdds tests/mime.test.ts covering both hardening paths.\n\nRefs #93\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: sanitize user input in MIME construction (GHSA-hvj8-v57h-f99m)",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-02T03:03:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ab381a55317c61c852b05065c7c2c729bb715471",
          "body": "Interpolating options.websiteUrl verbatim into the generated email's\nhref/src attributes allowed javascript:/data: schemes and\nattribute-breaking strings to be injected into the HTML body. Validate\nthat the resolved URL is a well-formed absolute https: URL via new URL()\nand throw otherwise; re-serialize from origin + pathname so the parser's\npercent-encoding neutralizes any attribute-breaking characters.\n\nRefs GHSA-6q8p-8fxx-wc7f\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: validate caller-supplied websiteUrl before embedding in HTML",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-02T03:02:30Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "b7675779bf00bfcf5d821086dec2671948946ebe",
          "body": "Resolve all open dependency CVEs (undici, vite) flagged in #91.\n\n- undici bumped to 7.28.0 (top-level) and 6.27.0 (under\n  @actions/http-client), fixing GHSA-vmh5-mc38-953g, GHSA-vxpw-j846-p89q,\n  GHSA-hm92-r4w5-c3mj, GHSA-p88m-4jfj-68fv, GHSA-pr7r-676h-xcf6,\n  GHSA-35p6-xmwp-9g52, GHSA-g8m3-5g58-fq\n[…]\nm\n  package tarball's bundled deps).\n\nnpm audit now reports 0 vulnerabilities. typecheck, build and the full\nvitest suite (162 tests) all pass.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "chore: update dependencies to resolve undici and vite CVEs",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-01T22:12:26Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f4debe00e35f7d8fe020619d04e4fbe9a3b94397",
          "body": "feat: report client version on every PKG and Cryptify request",
          "is_bot": false,
          "headline": "Merge pull request #90 from encryption4all/feat/client-version-header",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-19T12:08:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a11c139c2595e4c861f0afae40c2bdccb3b1cd9e",
          "body": "…build\n\nAddresses the two non-blocking review nits on #90:\n\n- detectHost() now recognises a Web Worker (WorkerGlobalScope/importScripts)\n  as host=browser. WASM crypto is commonly offloaded to a worker where\n  window/document are undefined, so that traffic was reported as unknown.\n  detectHost is no\n[…]\ntime `prepare` lifecycle\n  already regenerates version.ts from the bumped package.json and rebuilds,\n  so the exec build just doubled the work.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: attribute Web Worker traffic as browser; drop redundant release …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-19T11:56:11Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "261459cf59853ad34dffe4d4470dde730ace528e",
          "body": "The two new tests hard-coded host='node', which fails on the Deno/Bun\nintegration lanes where detectHost() correctly returns 'deno'/'bun'. Mirror\ndetectHost()'s ordering in the runtime assertion, and test comma-sanitisation\ndirectly via an exported sanitizeField helper instead of stubbing runtime\nglobals (which is order-sensitive under Deno). Implementation unchanged.",
          "is_bot": false,
          "headline": "test: make client-version tests runtime-agnostic",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-15T15:32:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7dfbe518b3f3074b7e90d3bd287ce3e987bb10b2",
          "body": "The SDK now stamps a 'host,host_version,pg-js,<version>' client-version header\n(reused from pg-pkg) onto every PKG and Cryptify request, so servers can attribute\ntraffic by SDK + version. A caller-supplied header (any casing) wins, so embedding\nhosts (e.g. the Outlook add-in sending pg4ol) keep thei\n[…]\n PostGuardBase (caller wins, case-insensitive).\n- Thread config.headers through the cryptify upload/download functions, which\n  previously hard-coded their headers and dropped config.headers entirely.",
          "is_bot": false,
          "headline": "feat: send X-POSTGUARD-CLIENT-VERSION on every request",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-15T15:06:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "70d435c2200d067caa73503e2c9a7028701bfc5b",
          "body": "…-identity\n\nfix(decrypt): surface verified private signing identity to recipients",
          "is_bot": false,
          "headline": "Merge pull request #89 from encryption4all/fix/expose-private-signing…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-08T12:47:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f08840b08ccacf42d13ed70e998cabacc217050",
          "body": "Drop the rationale comment in front of the unseal-result capture (the PR\ndescription already carries the why), and give the unsealAndCollect test\ndistinguishing public attributes so it actually locks in that the\npost-unseal VerificationResult overrides the pre-unseal sender.",
          "is_bot": false,
          "headline": "fix(decrypt): address PR feedback on verified-identity capture",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-05T15:28:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d76b07a301012ea6f596e8da2564f74a3fbf304f",
          "body": "`unsealAndCollect` was discarding the value returned by\n`unsealer.unseal(...)`. pg-wasm's `StreamUnsealer.unseal` resolves with\nthe full `VerificationResult { public, private? }` derived from the\ninner IBS signature after decryption, so dropping it threw away every\nattribute the sender signed under \n[…]\neds to\nchange.\n\nA unit test for `unsealAndCollect` against a stub unsealer locks the\nbehavior in: it resolves `unseal()` with `{public, private}` and\nasserts the returned `sender.private.con` matches.",
          "is_bot": false,
          "headline": "fix(decrypt): surface verified private signing identity to recipients",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-05T13:10:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "44e52504c003f77de9ad251855598293ec099621",
          "body": "…n-test\n\nfix(upload): pin onUploadInit acceptance with a regression test",
          "is_bot": false,
          "headline": "Merge pull request #88 from encryption4all/fix/onuploadinit-regressio…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:48:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a97dd9f7558f364aed2ef2b451bda5da3cd4f100",
          "body": "A prior runtime validator hand-maintained an allowlist of upload keys\nand started rejecting `onUploadInit` when the type was extended but\nthe allowlist wasn't. The validator was removed in #87, but that\nlanded under a `refactor:` prefix so semantic-release didn't cut a\nrelease and consumers on @e4a/\n[…]\nat asserts\n`sealed.upload({ onUploadInit })` does not throw \"unknown option\" —\npinning the contract so any future attempt to reintroduce a\nhand-maintained allowlist can't silently re-break the option.",
          "is_bot": false,
          "headline": "fix(upload): pin onUploadInit acceptance with a regression test",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:32:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "10261087db988e8190365e7683159cc496978e0f",
          "body": "…ts-onUploadInit\n\nrefactor(upload): remove runtime option validator, trust the types",
          "is_bot": false,
          "headline": "Merge pull request #87 from encryption4all/fix/upload-validator-accep…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:25:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f24762cdf850018990b33ebf752afbb096f9dd62",
          "body": "`validateUploadOptions` duplicated the shape of `UploadOptions` as a\nhand-maintained set of allowed keys + per-key value-type checks. The\ntype already encodes all of that — and when we recently added\n`onUploadInit` to `UploadOptions` we updated the type but forgot the\nvalidator, so callers passing t\n[…]\nfriendly compile-time error, and untyped JS callers\nwill get a downstream failure that's no worse than what most JS\nlibraries provide. No public API change; the deleted code path was\npurely defensive.",
          "is_bot": false,
          "headline": "refactor(upload): remove runtime option validator, trust the types",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:24:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d3a716870397fa1fc1bce427223c51ec10ab907d",
          "body": "…autounzip\n\nfeat(decrypt): onDownloadProgress callback + auto-unzip files",
          "is_bot": false,
          "headline": "Merge pull request #86 from encryption4all/feat/decrypt-progress-and-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T10:51:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c99027abd3f0aae9455455829c1f496bc545ba64",
          "body": "…sanitize, concurrency, tests\n\n- ProgressPipe now buffers the latest state so a callback attached\n  after bytes have flowed still receives one event. Fixes small-\n  payload case where inspect() drains the whole stream before\n  decrypt() can attach onDownloadProgress.\n- Restore DecryptFileResult.blob\n[…]\n-entry,\n  directory-filter, empty, and ordering-under-concurrency cases\n- api.test.ts: Content-Length parsing — numeric, 0, non-numeric, missing\n- download.test.ts: sanitizeDownloadFilename edge cases",
          "is_bot": false,
          "headline": "fix(decrypt): address review — small-payload progress, escape hatch, …",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:51:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7fb2083905e08f7fc91bbb78021be0e0b28d7af0",
          "body": "Lets consumers reference the package via `github:org/repo#branch` URLs\n(needed for cross-repo PRs where the website branch wants to test\nagainst an unpublished pg-js branch). npm installs devDependencies and\nruns prepare when fetching a git dep, building dist/ before the\npackage is wired into the consumer.",
          "is_bot": false,
          "headline": "chore: add prepare script so git-installs build automatically",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:31:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a4a4eeb4b9fa15316f4249fccdd1fd98087f2c8d",
          "body": "Adds an onDownloadProgress callback to DecryptInput so consumers can\nrender a real progress bar during decrypt(). The download stream is\ncreated in inspect() but bytes flow during decrypt(); a mutable-\ncallback ProgressPipe bridges this gap. Total size comes from\nContent-Length; when absent the call\n[…]\nntry shortcut (raw-data round trip) is\npreserved.\n\nBREAKING CHANGE: DecryptFileResult.files is now Array<{name, blob}>\n(was string[]); DecryptFileResult.blob is removed; download() takes\nno arguments.",
          "is_bot": false,
          "headline": "feat(decrypt): add onDownloadProgress + auto-unzip files",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:25:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "df02f3021a084d950e4c5050826c2c0a2c494020",
          "body": "ci: enable npm cache in setup-node to speed up Integration / Delivery",
          "is_bot": false,
          "headline": "Merge pull request #85 from encryption4all/ci/npm-cache-setup-node",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T08:20:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b3a5225d5dbf4bc0e2ea7d0b4a8f83df565c89ca",
          "body": "Add cache: 'npm' to all actions/setup-node@v6 steps in integration.yml\nand delivery.yml so npm ci benefits from package-lock.json-keyed caching.",
          "is_bot": false,
          "headline": "ci: enable npm cache in setup-node for faster installs",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:52:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1e16f117d5077948fc64612747e7569461b3922a",
          "body": "…utes\n\nfeat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
          "is_bot": false,
          "headline": "Merge pull request #78 from encryption4all/feat/condiscon-sign-attrib…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:45:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f532af8c43d44d85d5f1d8843f524e49df6ee9e4",
          "body": "- Add docstring to AttrConItem hinting at Array.isArray narrowing\n- Document that callers must not re-request the email attribute\n- Add test for empty-inner-array optional-discon convention",
          "is_bot": false,
          "headline": "fix: address dobby review comments",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:40:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a1dda70177a039c31509fb1719e9c21d9f77a52b",
          "body": "…cause\n\nfix: preserve original error as cause in unsealAndCollect",
          "is_bot": false,
          "headline": "Merge pull request #84 from encryption4all/fix/preserve-unseal-error-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:35:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5e7059e41c7866c19c5effc8c4d3ab08d98adcb3",
          "body": "perf: collect sealRaw output as chunks, single-allocate at end",
          "is_bot": false,
          "headline": "Merge pull request #83 from encryption4all/perf/sealraw-chunk-collect",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:34:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "291bf601e6319364918e7d1c914599d112e0f65d",
          "body": "…error\n\nfix(envelope): surface tier-3 upload failures instead of broken fallback",
          "is_bot": false,
          "headline": "Merge pull request #82 from encryption4all/fix/envelope-tier3-upload-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:33:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ce41f9b107d7545c5026040a999e1058381e044b",
          "body": "unsealAndCollect swallowed every error from `unsealer.unseal()` and\nre-threw `IdentityMismatchError`, masking transient failures, aborts,\nand WASM bugs. Forward `ErrorOptions` through `DecryptionError` and\n`IdentityMismatchError` so the original error is reachable via\n`.cause`, and let `AbortError` propagate unchanged.\n\nCloses #80",
          "is_bot": true,
          "headline": "fix: preserve original error as cause in unsealAndCollect",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:39:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "69b270e40bf94b98b4c598735b953856932c4bcf",
          "body": "sealRaw was reallocating and copying the entire prior buffer on every\nwrite — O(N²·c) bytes copied for N chunks of average size c. Mirror the\ndecrypt.ts:218-224 pattern: collect chunks during streaming, then\nallocate once and copy each into place. Output bytes unchanged.\n\nCloses #81",
          "is_bot": true,
          "headline": "perf: collect sealRaw output as chunks, single-allocate at end",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:38:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d728838eead306050f2d1f94af6eb86deef66928",
          "body": "…broken fallback\n\nTier 3 has no local attachment, so swallowing a Cryptify upload rejection produced an envelope whose manual-upload body pointed the recipient at a file that does not exist. Re-throw the error on tier 3; tier 2 still falls through to manual-upload (attachment is the fallback) but now warns instead of being fully silent.\n\nCloses #79",
          "is_bot": true,
          "headline": "fix(envelope): surface tier-3 upload failures instead of producing a …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:38:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fbe1dec13061fc62faef54f2fb0c592684e5c615",
          "body": "Extend `YiviSign.attributes` from `AttrReq[]` to `AttrConItem[]`, where\neach entry is either a single attribute (legacy flat shape — keep\n`optional: true` for the skip-one case) or a Yivi\ndisjunction-of-conjunctions `AttrReq[][]`. An empty inner array marks\nthe discon as optional, mirroring Yivi nat\n[…]\nr, additive bump.\n\nTests: new `buildStartRequestBody` block in `tests/postguard.test.ts`\ncovers the no-attr, email-bound, legacy-flat-with-optional, and\nmixed-Single/Discon cases. `npm test`: 115/115.",
          "is_bot": false,
          "headline": "feat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-01T13:40:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e11942da25a6470c76be42f044566deca5a916ff",
          "body": "…-notice\n\nfeat(upload): info notice when notify is unset on first upload",
          "is_bot": false,
          "headline": "Merge pull request #77 from encryption4all/feat/upload-silent-default…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:44:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "61b4459270d5ca36a553358213bf5edcf7301502",
          "body": "Closes the one failure mode neither TypeScript nor validateUploadOptions\ncan catch: a caller who simply forgets to set `notify` and silently gets\nno recipient email. `notify` is optional in `UploadOptions`, so leaving\nit out type-checks cleanly — the user only finds out via a support\nticket that \"th\n[…]\nt) so long-running\nprocesses don't flood. Suppressed by passing `notify` explicitly:\n`{ notify: { recipients: true } }` to email, or\n`{ notify: { recipients: false } }` to keep silent intent explicit.",
          "is_bot": false,
          "headline": "feat(upload): info notice when notify is unset on first upload",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:42:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b6d972601de45fd51a5094f39819fe788e992e70",
          "body": "…upport\n\nfeat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
          "is_bot": false,
          "headline": "Merge pull request #76 from encryption4all/feat/non-browser-runtime-s…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:32:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c3f3e78697455c6ba6abf69704a506fe1af189c0",
          "body": "tsdown@0.22 requires Node ^22.18 || >=24; it can't build on Node 20.\nSince we don't intend to claim Node 20 support, drop it from the matrix\nand bump engines.node to >=22 to match. README and CLAUDE.md updated to\nreflect Node 22 as the floor.",
          "is_bot": false,
          "headline": "ci: drop Node 20 from matrix, require Node 22+",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:23:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f8b2080bf71cfffc65ce35e63eb37e8182f62b6",
          "body": "Addresses inline comments from dobby-coder's review:\n\n- src/util/zip.ts: narrow the conflux self shim to save/restore around\n  the dynamic import instead of permanently mutating globalThis.self.\n  Avoids surprising downstream libraries that use `typeof self !==\n  'undefined'` as a browser-detection \n[…]\nns surface here as a CI failure\n  on this SDK rather than confused for upstream breakage.\n\n- README.md: documents the minimum Bun (1.0.16+) and Deno (1.39+)\n  versions — both gated by AbortSignal.any.",
          "is_bot": false,
          "headline": "refactor(runtime): apply review feedback from #76",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:04:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "107870acfef70baf59ea8647b087f5e028cb2526",
          "body": "README gets a \"Server-side usage\" section listing what works (encrypt +\nupload via sign.apiKey or sign.session), what's browser-only (sign.yivi,\nresult.download), and how to run scripts/smoke.mjs.\n\nCLAUDE.md gets a runtime-support section plus notes on the two\nnon-obvious gotchas (FileList guard, conflux self shim) so future\ncontributors don't accidentally regress them.",
          "is_bot": false,
          "headline": "docs: document Node, Bun, and Deno runtime support",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c2cbf0c9239ffbe35d26639710de546ec694ca87",
          "body": "Adds three CI jobs to integration.yml: Node (matrix over 20/22/24), Bun,\nand Deno. Each lane builds the dist and runs scripts/smoke.mjs in dry\nmode, which loads the dist, exercises createZipReadable, and verifies\nglobal availability — catches both bugs fixed in the previous commit\nwithout needing live credentials.\n\nThe smoke script can also do a full live upload when PG_API_KEY is set;\nuseful for one-off manual verification.",
          "is_bot": false,
          "headline": "ci: matrix-test Node 20/22/24, Bun, and Deno with smoke script",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2adff7ae11d0de16f6d2367518791f3032ce6ad0",
          "body": "Two bugs prevented the SDK from working in non-browser runtimes:\n\n  1. `instanceof FileList` in sealed.ts throws ReferenceError when\n     FileList is not a global. FileList is browser-only; Node, Bun, and\n     Deno don't have it. Now typeof-guarded.\n\n  2. `@transcend-io/conflux/dist/esm/bigint.js` r\n[…]\nlower bound is required\nfor AbortSignal.any, which the encrypt pipeline uses.\n\nVerified end-to-end against staging.postguard.eu under Node 26, Bun\n1.3.14, and Deno 2.8.0 (real UUID returned for each).",
          "is_bot": false,
          "headline": "feat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "89b07223ca5600097020ee9356f468e6ea95419a",
          "body": "Captures the prebuild generator step (base64 WASM + wasm-bindgen shim\npatch), the lazy builder surface, and the Conventional Commits\nrequirement so future contributors don't have to reverse-engineer them.",
          "is_bot": false,
          "headline": "docs: add CLAUDE.md with build, architecture, and pg-wasm patching notes",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:44:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b9c2f20ebe01b9fc436aaad9e709ba8cc6c356b",
          "body": "Surfaces the most common misconfigurations (boolean notify, top-level\nrecipients, typos in notify keys, non-object opts) as synchronous\nTypeErrors with messages that point at the correct shape. Previously\nthese silently degraded to \"no notification email sent\" because every\nfield coalesced to undefined and Cryptify defaulted to silent.",
          "is_bot": false,
          "headline": "feat(upload): validate upload options shape with clear errors",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3af134536d127d14ad1866c33e81b44b68feae11",
          "body": "chore: bump vitest to 4.1.7",
          "is_bot": false,
          "headline": "Merge pull request #75 from encryption4all/chore/vitest-4.1.7",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-21T11:55:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2dabb9351eac9577a43bb89d7af8ef1a5514397a",
          "body": null,
          "is_bot": true,
          "headline": "chore: bump vitest to 4.1.7",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-20T22:20:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "39a877af5275796e53390fb56131050c6bd47629",
          "body": "chore: update dependencies",
          "is_bot": false,
          "headline": "Merge pull request #73 from encryption4all/chore/update-deps-2026-05-16",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-17T15:59:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c6905cfa83e24601b15ad725ab5a9e0d917e0af2",
          "body": "- @e4a/pg-wasm 0.6.0 -> 0.6.1\n- @privacybydesign/yivi-client 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-core 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-css 1.0.0-beta.7 -> 1.0.1\n- @privacybydesign/yivi-web 1.0.0-beta.6 -> 1.0.1\n\nRefs #72",
          "is_bot": true,
          "headline": "chore: update dependencies",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-16T22:18:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dcdd6591f58976364f8220f4cedbe86d0e2bce3b",
          "body": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-chunk",
          "is_bot": false,
          "headline": "Merge pull request #71 from encryption4all/feat/upload-init-callback",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-16T12:48:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b8a483ab5d9dc72b20eec89eab6dc8c8da5032af",
          "body": "…chunk\n\nAdds `onUploadInit?: (info: { uuid: string; recoveryToken: string }) => void`\nto `UploadOptions` and `CreateEnvelopeOptions`. Fires once, synchronously\nafter `upload_init` resolves and before the first chunk PUT, so callers\n(Outlook/Thunderbird addons) can persist `{uuid, recoveryToken}` to \n[…]\nam, and\nforwarded through createEnvelope.\n\nCloses #68\nRefs encryption4all/postguard-tb-addon#103\nRefs encryption4all/postguard-outlook-addon#82\n\nCo-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-16T12:43:56Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "9453662c37e327d1d854a77a12f9ab008ae54dde",
          "body": "chore: bump @e4a/pg-wasm to 0.6.0",
          "is_bot": false,
          "headline": "Merge pull request #70 from encryption4all/chore/pg-wasm-0.6.0",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-16T10:24:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb457cac640c13d4d33b2767cfcf951a8875badc",
          "body": "Tracks postguard pg-core-v0.6.0. Public TypeScript API surface is unchanged; only internal wasm-export symbol order shifted and ReadableStreamReaderMode became exported.\n\nCloses #69",
          "is_bot": true,
          "headline": "chore: bump @e4a/pg-wasm to 0.6.0",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T22:14:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "146a7ab70ea8acc6071a4c773a8ae467c1c391a9",
          "body": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
          "is_bot": false,
          "headline": "Merge pull request #67 from encryption4all/fix/data-bin-unwrap-39",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T16:29:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "959a0c2526aa8cee5ee4273635885953d864aa59",
          "body": "Restores symmetry with Sealed.upload() data: mode, which wraps raw bytes\nas a single-entry zip named data.bin. When Opened.decrypt() sees a uuid\nresult whose central directory is exactly ['data.bin'], it now returns\nDecryptDataResult { plaintext, sender } instead of forcing consumers to\nwalk the zip\n[…]\nven (conflux's streaming\nwriter leaves compressedSize=0 in the LFH, so LFH-only walking fails)\nand supports stored (method 0) and deflate (method 8) via\nDecompressionStream('deflate-raw').\n\nCloses #39",
          "is_bot": true,
          "headline": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T16:22:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "afc9b71ad518127f8bc60bd7923cc8d6e6637c45",
          "body": "chore: update vitest to 4.1.6",
          "is_bot": false,
          "headline": "Merge pull request #64 from encryption4all/chore/vitest-4.1.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T15:53:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6205fc309aaf954e82937beae723912812604f2e",
          "body": "…y-token\n\nfeat(api): capture recovery_token and add resumeUpload for cross-restart resume",
          "is_bot": false,
          "headline": "Merge pull request #66 from encryption4all/feat/upload-resume-recover…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T15:42:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3838012e3a1883ae295e3baef404def79b951caf",
          "body": "…art resume\n\nWires the cross-refresh-resume protocol on the SDK side (cryptify#148):\n\n- initUpload now reads the snake-case recovery_token field from the JSON\n  body and returns it on FileState as recoveryToken.\n- New resumeUpload(cryptifyUrl, uuid, recoveryToken) calls\n  GET /fileupload/{uuid}/stat\n[…]\ntoken cases per cryptify's\n  info-hiding behaviour).\n- FileState and resumeUpload re-exported from the package root so\n  consumers (Office addon, Thunderbird, website) can own persistence.\n\nCloses #65",
          "is_bot": true,
          "headline": "feat(api): capture recovery_token and add resumeUpload for cross-rest…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T14:55:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e07f68e0b1870eaa7279e0d3fb3d636076db2b8b",
          "body": "Patch bump from 4.1.5 → 4.1.6. Closes #63.",
          "is_bot": true,
          "headline": "chore: update vitest to 4.1.6",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-12T22:17:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4b78ab4dadc50e9df1578db6f8080abeba07eefd",
          "body": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
          "is_bot": false,
          "headline": "Merge pull request #61 from encryption4all/fix/jwt-cache-bound-sweep",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-12T07:23:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a8a9a25ddbad8f76ab04f65ee453b5730ff6a9df",
          "body": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
          "is_bot": false,
          "headline": "Merge pull request #62 from encryption4all/fix/skip-base64-tier-2-3",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-12T07:20:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "53a6db6c42c22c0537149750cf27bb7453ed4544",
          "body": "Tier 2/3 envelopes never embed base64 in the body — they use Cryptify\nupload URLs. Computing the full base64 string just to feed pickTier\nwasted CPU and a ciphertext-sized intermediate String (~33% larger\nthan the bytes themselves) that was immediately discarded.\n\nDerive the base64 length arithmetic\n[…]\nd only call uint8ArrayToBase64 inside the tier-1 branch.\nAlso replaces the char-by-char btoa loop with a chunked\nString.fromCharCode(...subarray) accumulator for the remaining tier-1\npath.\n\nCloses #58",
          "is_bot": true,
          "headline": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-11T23:39:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f5986dd19a25f5bd674ed046ae91695549c9ae97",
          "body": "Cap jwtCache at 100 entries with LRU eviction so long-lived SPA sessions\nthat decrypt for many recipients can't grow the Map indefinitely. On every\ncacheJwt write, sweep entries past their exp (with the same 30s margin used\non read) so expired-but-unread keys don't linger.\n\nCloses #59",
          "is_bot": true,
          "headline": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-11T23:39:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cd3b05d16e5964cb2681d28e30637e68d243ba8a",
          "body": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
          "is_bot": false,
          "headline": "Merge pull request #57 from encryption4all/chore/bump-deps-2026-05-10",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-10T22:37:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2e6ad1abf96d19154448c0ae494e5fd87985b8a5",
          "body": null,
          "is_bot": true,
          "headline": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-10T22:19:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a60716e0b4eaaed0f3763a2eebbcf6c39fc0560d",
          "body": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
          "is_bot": false,
          "headline": "Merge pull request #54 from encryption4all/chore/release-yivi-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:44:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d282facf84bb1d1669c7c38f3bee9deda06317d8",
          "body": null,
          "is_bot": false,
          "headline": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "837382d71df3438efb7c42b78ec5b7065bc70f68",
          "body": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
          "is_bot": false,
          "headline": "Merge pull request #53 from encryption4all/chore/yivi-1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:41:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1ace739a67a87f547369e926b66adc4313d0c5e7",
          "body": null,
          "is_bot": false,
          "headline": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:40:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "95af57cc8bfb42d42baf9ac04c5293c4b9b1abaa",
          "body": "feat(download): resume via Range header on transient stream failures",
          "is_bot": false,
          "headline": "Merge pull request #52 from encryption4all/feat/resumable-downloads",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T19:00:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e9f60c44b27f10b035905e221a188aed53332ea5",
          "body": "Cryptify's `FileServer` already supports HTTP Range, so a stream-level\nfailure mid-download (network drop, idle timeout) can now resume from\nthe byte offset reached rather than restarting from zero. The consumer\nsees a single contiguous stream regardless of how many internal retries\nhappened.\n\nImple\n[…]\nqueued\nchunks, so the test-only `streamThenError` is now pull-based to\nguarantee chunks are delivered before the error fires.\n\n72 tests pass; type-check clean.\n\nCloses #48.\nRefs #117, #136, #145, #47.",
          "is_bot": false,
          "headline": "feat(download): resume via Range header on transient stream failures",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:44:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e55302aab22e429917fb57704331af80da233026",
          "body": "ci: add semantic PR title check workflow",
          "is_bot": false,
          "headline": "Merge pull request #51 from encryption4all/ci/semantic-pr-title",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:38:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "629fff28fd173b4737601bd8dd755cfd93f8f73b",
          "body": "Closes #49",
          "is_bot": false,
          "headline": "ci: add semantic PR title check workflow",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:36:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b0f0dd871b7bdd25e652ce75b6c74da010e7e59",
          "body": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
          "is_bot": false,
          "headline": "Merge pull request #50 from encryption4all/chore/yivi-beta-5",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:18:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9d4f49e7d71b49311f4541c6cc3edc1bc3f46b17",
          "body": null,
          "is_bot": false,
          "headline": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:14:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3f52a9a7eb25ebf0c7b6555a0f7897219fd7b41d",
          "body": "feat(upload): retry chunks and downloads with exponential backoff",
          "is_bot": false,
          "headline": "Merge pull request #47 from encryption4all/feat/upload-retry-backoff",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T12:11:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4081e1bdf44e0dd8d27783a85dce0ea3c9058bdc",
          "body": "Three small follow-ups from @dobby-coder's review:\n\n1. Flatten `throwSessionExpiredOrNetworkError` — replace the\n   throw-inside-try / catch-and-rethrow pattern with a flatter\n   parse-then-decide shape. Same behaviour, no `instanceof` re-check\n   needed in the catch.\n\n2. Document `downloadTimeoutMs\n[…]\nIDE go-to-definition. No runtime change.\n\n`AbortSignal.any` is left as-is — it's already used in\n`src/crypto/encrypt.ts:45`, so this PR doesn't introduce a new\nbrowser-support floor.\n\nRefs #47 review.",
          "is_bot": false,
          "headline": "refactor(retry): apply review feedback from #47",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T11:46:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 49,
      "commits_last_year": 210,
      "latest_release_at": "2026-07-17T05:57:23Z",
      "latest_release_tag": "v2.3.1",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 16,
      "days_since_latest_release": 4,
      "mean_days_between_releases": 1.6
    },
    "community": {
      "has_readme": true,
      "has_license": false,
      "has_description": false,
      "has_contributing": false,
      "health_percentage": 25,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "@e4a/pg-js",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "postguard",
            "ibe",
            "encryption",
            "identity-based-encryption",
            "yivi",
            "irma"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/@e4a/pg-js",
          "is_deprecated": false,
          "latest_version": "2.3.1",
          "repository_url": "https://github.com/encryption4all/postguard-js",
          "versions_count": 51,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 3,
          "monthly_downloads": 4063,
          "first_published_at": "2026-03-25T14:29:26.192000Z",
          "latest_published_at": "2026-07-17T05:57:22.376000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 4
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 0,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_stars": 0
      },
      "open_issues_and_prs": 6
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "tsconfig.json"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 41642,
      "source_files_sampled": 60,
      "oversized_source_files": 0,
      "agent_instruction_files": [
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 10266
    },
    "dependencies": {
      "manifests": [
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "npm"
      ],
      "dependencies": [
        {
          "name": "@e4a/pg-wasm",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.6.1"
        },
        {
          "name": "@privacybydesign/yivi-client",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.0"
        },
        {
          "name": "@privacybydesign/yivi-core",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.0"
        },
        {
          "name": "@privacybydesign/yivi-css",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.1"
        },
        {
          "name": "@privacybydesign/yivi-web",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.1"
        },
        {
          "name": "@transcend-io/conflux",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^6.1.3"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 2,
        "merged_prs": 75,
        "open_issues": 4,
        "closed_ratio": 0.897,
        "closed_issues": 35,
        "closed_unmerged_prs": 1
      },
      "bus_factor": 1,
      "bot_contributors": 1,
      "top_contributors": [
        {
          "type": "User",
          "login": "rubenhensen",
          "commits": 177,
          "avatar_url": "https://avatars.githubusercontent.com/u/17710718?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "delivery.yml",
        "integration.yml",
        "pr-title.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "package-lock.json"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 5,
            "reason": "branch protection is not maximal on development and all release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 8,
            "reason": "Found 4/5 approved changesets -- score normalized to 8",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 0,
            "reason": "license file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 5,
            "reason": "dependency not pinned by hash detected -- score normalized to 5",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 9,
            "reason": "1 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "6c0f55443706ba27fda1db73f48655795cb151d9",
        "ran_at": "2026-07-22T02:15:14Z",
        "aggregate_score": 5.3,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-21T14:57:55Z",
      "oldest_open_prs": [
        {
          "number": 112,
          "created_at": "2026-07-14T23:40:01Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 114,
          "created_at": "2026-07-14T23:41:39Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ],
      "last_merged_pr_at": "2026-07-21T14:56:49Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 94,
          "created_at": "2026-07-01T23:13:54Z",
          "last_comment_at": "2026-07-02T03:55:34Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 105,
          "created_at": "2026-07-14T23:36:01Z",
          "last_comment_at": "2026-07-14T23:37:02Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 107,
          "created_at": "2026-07-14T23:36:23Z",
          "last_comment_at": "2026-07-14T23:37:04Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 110,
          "created_at": "2026-07-14T23:36:50Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/encryption4all/postguard-js",
    "host": "github.com",
    "name": "postguard-js",
    "owner": "encryption4all"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 57,
      "inputs": {
        "security": 53,
        "vitality": 85,
        "community": 24,
        "governance": 60,
        "engineering": 57
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "excellent",
        "name": "Vitality",
        "value": 85,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 75,
            "inputs": {
              "commits_last_year": 210,
              "human_commit_share": 0.74,
              "days_since_last_push": 0,
              "active_weeks_last_year": 16
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "16/52 weeks with commits",
                "points": 11.1,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 16
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "210 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 210
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "releases_count": 49,
              "latest_release_tag": "v2.3.1",
              "releases_from_tags": false,
              "days_since_latest_release": 4,
              "mean_days_between_releases": 1.6
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "49 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 49
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 4 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~1.6 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.6
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "critical",
        "name": "Community & Adoption",
        "value": 24,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 0,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "0 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "critical",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 25,
            "inputs": {
              "has_readme": true,
              "has_license": false,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "no license file detected",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "license_absent",
                    "params": {}
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 60,
            "inputs": {
              "packages": [
                "@e4a/pg-js"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 4063
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "4,063 downloads/month across npm",
                "points": 48.1,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 4063,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 60,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "excellent",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 92,
            "inputs": {
              "merged_prs": 75,
              "open_issues": 4,
              "closed_issues": 35,
              "issue_closed_ratio": 0.897,
              "closed_unmerged_prs": 1
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "90% of issues closed",
                "points": 41.9,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 90
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "75/76 decided PRs merged",
                "points": 37.7,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 75,
                      "decided": 76
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 4/5 approved changesets -- score normalized to 8",
                "points": 12,
                "status": "partial",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 56,
            "inputs": {
              "followers": 6,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "encryption4all",
              "public_repos": 16,
              "account_age_days": 1992
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "organization-owned",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "owner_organization",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "6 followers of encryption4all",
                "points": 6.1,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 6,
                      "login": "encryption4all"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "16 public repos, account ~5 yr old",
                "points": 19.9,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 16
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 5
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "@e4a/pg-js"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 4
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on npm",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 4 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "51 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 51
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "moderate",
        "name": "Engineering Quality",
        "value": 57,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 68,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "at_risk",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": false,
              "has_description": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "moderate",
        "name": "Security",
        "value": 53,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 53,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 17,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 1,
              "scorecard_aggregate": 5.3
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection is not maximal on development and all release branches",
                "points": 3.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 4/5 approved changesets -- score normalized to 8",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 5",
                "points": 2.5,
                "status": "partial",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "1 existing vulnerabilities detected",
                "points": 6.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 1
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "good",
        "name": "AI Readiness",
        "value": 75,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 1,
              "agent_instruction_files": [
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 10266
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "CLAUDE.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "CLAUDE.md"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "74 of 74 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 74,
                      "sampled": 74
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 58,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "package-lock.json"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [
                "tsconfig.json"
              ],
              "agent_commit_share": 0.13,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "tsconfig.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "tsconfig.json"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "13 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 13,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 5",
                "points": 5,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "primary_language": "TypeScript",
              "largest_source_bytes": 41642,
              "source_files_sampled": 60,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "TypeScript (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "TypeScript"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/60 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 60,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
    "deps.dev does not index npm:@e4a/pg-js@2.3.1; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-22T02:15:30.722115Z",
  "schema_version": "0.26.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/e/encryption4all/postguard-js.svg",
  "full_name": "encryption4all/postguard-js",
  "license_state": "absent",
  "license_spdx": null
}

Оцінки — це сигнали, а не гарантії. Вони відображають публічно видимі практики на GitHub — це не аудит коду й не гарантія безпеки.

Відсутні дані виключаються, а ваги перенормовуються — нуль за відсутність ніколи не ставиться. Методологія версіонована й відкрита: метрики v1.13.0, схема v0.26.0 — повна методологія · вікі метрик.

Як окремий результат виглядає на тлі всього реєстру: сукупна статистикаnpm.