公开记录
软件健康报告模式 0.23.0 · 指标 1.13.0 · 2026-07-21 21:02 UTC

Betterment / delayed

a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process millions of background jobs per day

RubyMIT★ 175 星标⑂ 12 复刻始于 2021年8月在 GitHub 上查看 ↗

Betterment/delayed 的健康指数为 100 分中的 71 分,处于「良好」区间。 其得分最高的类别是Sustainability & Governance(82/100),最低的是AI Readiness(51/100)。 最近一次更新在 8 天前。 近期的大部分工作由 3 位贡献者完成。

71
总分 / 100
良好

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 49(有风险)的上限。AI 就绪度不计入总体分。

71
优秀85-100堪称典范;基本满足所有检验标准
良好70-84健康;仅有轻微不足
中等50-69可接受,但存在明显不足;建议进行审查
存在风险30-49存在重大薄弱环节;采用时应保持审慎
危急1-29问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

所有权

105 关注者91 个公开仓库始于 2011年6月

该仓库由组织支持——共同承担、可问责的托管责任,可延续于任何单一维护者之后。

软件包生态系统

注册表软件包版本月下载量版本数最近发布
RubyGemsdelayed3.1.0-298 天前

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

78良好 · 占总体的 22%
评分方式
28.8/36推送新近度 — 最近一次推送于 8 天前
12.5/36提交节奏 — 52 周中有 18 周有提交
15.5/18提交量 — 最近一年 52 次提交
7/10OpenSSF Scorecard:Maintained — 9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7
所用输入
commits_last_year52
human_commit_share1
days_since_last_push8
active_weeks_last_year18

发布纪律

100优秀
评分方式
27/27有发布版本 — 已发布 26 个发布版本
36/36发布时效 — 最近一次发布版本于 8 天前
27/27发布节奏 — 约每 31.1 天发布一次
0/10OpenSSF Scorecard:Signed-Releases — 无数据
所用输入
releases_count26
latest_release_tagv3.1.0
releases_from_tags
days_since_latest_release8
mean_days_between_releases31.1
已排除计分(无数据或不适用):OpenSSF Scorecard:Signed-Releases。 其余权重已重新归一化。

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

59中等 · 占总体的 18%
评分方式
36.3/60星标 — 175 个星标
8.7/25复刻 — 12 个复刻
9.1/15关注者 — 44 位关注者
所用输入
forks12
stars175
watchers44
growth_stateorganic
growth_factor_pct100

社区健康

50中等
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(MIT)
0/18CONTRIBUTING 指南
0/13.5行为准则
0/7.2议题模板
0/6.3PR 模板
所用输入
has_readme
has_license
has_contributing
has_issue_template
has_code_of_conduct
has_pull_request_template
评分方式
62.9/80累计下载量 — rubygems 合计历史 1,132,655 次下载
0/20注册表被依赖数 — 该生态系统不报告此项
所用输入
packagesdelayed
dependents
ecosystemsrubygems
total_downloads1,132,655
monthly_downloads
已排除计分(无数据或不适用):注册表被依赖数。 其余权重已重新归一化。

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

82良好 · 占总体的 24%
评分方式
36/54巴士系数 — 3 位贡献者贡献了半数提交
16.3/22.5提交分布 — 头号贡献者编写了 28% 的提交
13.5/13.5贡献者广度 — 100 位贡献者
10/10OpenSSF Scorecard:Contributors — project has 34 contributing companies or organizations
所用输入
bus_factor3
contributors_sampled100
top_contributor_share0.275
评分方式
35.5/46.8议题解决 — 76% 的议题已关闭
35.2/38.3PR 接受 — 已裁定的 PR 中 81/88 已合并
15/15OpenSSF Scorecard:Code-Review — all changesets reviewed
所用输入
merged_prs81
open_issues6
closed_issues19
issue_closed_ratio0.76
closed_unmerged_prs7
评分方式
30/30所有权背书 — 组织持有
0/20已验证域名
14.6/25所有者影响力 — Betterment 有 105 位关注者
25/25既往记录 — 91 个公开仓库,账户约 15 年
所用输入
followers105
owner_typeOrganization
is_verified
owner_loginBetterment
public_repos91
account_age_days5,507
评分方式
25/25已发布且可解析 — rubygems 上有 1 个软件包
35/35发布时效 — 最近一次发布于 8 天前
20/20版本历史 — 29 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesdelayed
ecosystemsrubygems
any_deprecated
min_days_since_publish8

工程质量

基础的工程与文档实践是否到位?

70良好 · 占总体的 20%

工程实践

84良好
评分方式
24/24CI 工作流 — 2 个工作流
24/24存在测试
16/16Linter 配置 — .rubocop.yml
0/9.6Pre-commit 钩子
0/6.4.editorconfig
20/20OpenSSF Scorecard:CI-Tests — 30 out of 30 merged PRs checked by a CI test -- score normalized to 10
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config

文档

50中等
评分方式
30/30README
0/25文档目录
0/15文档 / 主页站点
10/10仓库描述
0/10主题标签
10/10Wiki
所用输入
topics
has_wiki
homepage
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

59中等 · 占总体的 16%

安全态势

53中等
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
6/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 30 out of 30 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
7.5/7.5Code-Review — all changesets reviewed
2.5/2.5Contributors — project has 34 contributing companies or organizations
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
5.2/7.5Maintained — 9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7
0/5Packaging — 无数据
5/5Pinned-Dependencies — all dependencies are pinned
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
0/7.5Vulnerabilities — 53 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate5.3
已排除计分(无数据或不适用):packaging, signed_releases。 其余权重已重新归一化。
评分方式
26.6/35直接依赖不含已知公告 — 1 个受影响:activerecord 8.0.2 (moderate)
0/25间接依赖不含已知公告 — 在此范围内,传递依赖集合无法与开发和测试依赖区分
37.2/40没有长期未处理的公告 — 1 个携带公告的软件包超过 90 天未处理;最早一条发布于 341 天前
所用输入
sourceosv
advisories53
affected_packages8
assessed_packages78
unassessed_packages2
affected_by_severitycritical 1, high 4, moderate 2, low 1
direct_affected_packages1
已排除计分(无数据或不适用):间接依赖不含已知公告。 其余权重已重新归一化。 已将 78 个已解析依赖与 OSV 比对。 有 2 项无法评估——没有已解析的版本、生态系统不受支持,或超出所报告的软件包清单。 该仓库未发布任何索引可解析的软件包,因此改为评估仓库依赖图。该图将开发与测试版本固定同交付的依赖混在一起,因此仅对声明的运行时依赖计分;传递性发现仅作为背景信息列出,不计入评分。 未对可达性进行分析。

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?这是一枚独立的实验性徽章——权重为 0.0,因此单独呈现,不影响总体健康评分。

51中等 · 占总体的 0%
评分方式
0/45代理指令 — 没有 CLAUDE.md / AGENTS.md / 编辑器规则
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 97 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.97
agent_instruction_files
agent_instruction_max_bytes
评分方式
0/18一条命令的引导启动
22/22自动化测试
11/11Lint / 格式化配置 — .rubocop.yml
0/11静态类型检查
10/10可复现环境 — lockfile
4/10已体现的代理实践 — 最近 100 次提交中有 2 次由代理编写或署名代理
0/8自动化维护 — 未观察到自动依赖更新
10/10OpenSSF Scorecard:Pinned-Dependencies — all dependencies are pinned
所用输入
has_nix
has_tests
lockfilesGemfile.lock
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0.02
toolchain_manifests
dependency_bot_commit_share0
评分方式
0/45可类型检查的代码 — Ruby,未配置类型检查
55/55可控的文件大小 — 采样的 58 个源文件中有 0 个超过 60KB
所用输入
primary_languageRuby
largest_source_bytes48,940
source_files_sampled58
oversized_source_files0

关键数据

175GitHub 星标
100贡献者
52最近 12 个月提交数
8距最近推送天数
26发布版本数
3巴士系数(bus factor)
6开放议题
RubyGems软件包生态系统数

更多细节

Star 与 Fork 历史 175 ★ / 12 ⇿
175Star
12Fork
25发布

每颗 star 和每个 fork 的添加时间,来自 GitHub 并按天汇总。累计增长位于其构成来源——每日新增——的正上方,二者可相互对照:稳定的自然增长与短暂的突增形态截然不同。当这一差别可被衡量时,它会作为增长真实性予以报告。

0408012016020017512162021-082024-012026-07
主版本 3次版本 12修订 10

每个点涵盖 5 天。

OpenSSF Scorecard 5.3 / 10
5.3综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。Scorecard v5.5.0 · 2026-07-21 21:02 UTC

10Binary-Artifactsno binaries found in the repo
8Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests30 out of 30 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
10Code-Reviewall changesets reviewed
10Contributorsproject has 34 contributing companies or organizations
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
7Maintained9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7
不适用Packagingpackaging workflow not detected
10Pinned-Dependenciesall dependencies are pinned
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
0Vulnerabilities53 existing vulnerabilities detected
直接依赖 13
注册表软件包版本约束清单文件
RubyGemsactionmailerGemfile
RubyGemsactivejobGemfile
RubyGemsactiverecordGemfile
RubyGemsappraisalGemfile
RubyGemsbetterlintGemfile
RubyGemsmysql2Gemfile
RubyGemspgGemfile
RubyGemsrakeGemfile
RubyGemsrspecGemfile
RubyGemstimecopGemfile
RubyGemszeitwerkGemfile
RubyGemssqlite3>= 1.4Gemfile
RubyGemssnapshot_testingGemfile
全部依赖 80

来自 GitHub 依赖图的完整解析依赖集合:14 个直接依赖与 66 个间接(传递)软件包。仓库提交锁文件时,传递闭包才是完整的。

注册表软件包版本关系
RubyGemsactionmailer8.0.2直接
RubyGemsactivejob8.0.2直接
RubyGemsactiverecord直接
RubyGemsactiverecord8.0.2直接
RubyGemsappraisal2.5.0直接
RubyGemsbetterlint1.19.0直接
RubyGemsmysql20.5.6直接
RubyGemspg1.5.9直接
RubyGemsrake13.2.1直接
RubyGemsrspec3.13.0直接
RubyGemssnapshot_testing0.3.4直接
RubyGemssqlite32.6.0直接
RubyGemstimecop0.9.10直接
RubyGemszeitwerk2.7.2直接
RubyGemsactionpack8.0.2间接
RubyGemsactionview8.0.2间接
RubyGemsactivemodel8.0.2间接
RubyGemsactivesupport8.0.2间接
RubyGemsast2.4.3间接
RubyGemsbase640.2.0间接
RubyGemsbenchmark0.4.0间接
RubyGemsbigdecimal3.1.9间接
RubyGemsbuilder3.3.0间接
RubyGemsconcurrent-ruby间接
RubyGemsconcurrent-ruby1.3.5间接
RubyGemsconnection_pool2.5.0间接
RubyGemscrass1.0.6间接
RubyGemsdate3.4.1间接
RubyGemsdiff-lcs1.6.1间接
RubyGemsdrb2.2.1间接
RubyGemserubi1.13.1间接
RubyGemsglobalid1.2.1间接
RubyGemsi18n1.14.7间接
RubyGemsjson2.10.2间接
RubyGemslanguage_server-protocol3.17.0.4间接
RubyGemslint_roller1.1.0间接
RubyGemslogger1.7.0间接
RubyGemsloofah2.24.0间接
RubyGemsmail2.8.1间接
RubyGemsmini_mime1.1.5间接
RubyGemsminitest5.25.5间接
RubyGemsnet-imap0.5.6间接
RubyGemsnet-pop0.1.2间接
RubyGemsnet-protocol0.2.2间接
RubyGemsnet-smtp0.5.1间接
RubyGemsnokogiri1.18.6间接
RubyGemsparallel1.26.3间接
RubyGemsparser3.3.7.3间接
RubyGemsprism1.4.0间接
RubyGemsracc1.8.1间接
RubyGemsrack3.1.12间接
RubyGemsrack-session2.1.0间接
RubyGemsrack-test2.2.0间接
RubyGemsrails-dom-testing2.2.0间接
RubyGemsrails-html-sanitizer1.6.2间接
RubyGemsrainbow3.1.1间接
RubyGemsregexp_parser2.10.0间接
RubyGemsrspec-core3.13.3间接
RubyGemsrspec-expectations3.13.3间接
RubyGemsrspec-mocks3.13.2间接
RubyGemsrspec-support3.13.2间接
RubyGemsrubocop1.75.1间接
RubyGemsrubocop-ast1.43.0间接
RubyGemsrubocop-capybara2.22.1间接
RubyGemsrubocop-factory_bot2.27.1间接
RubyGemsrubocop-graphql1.5.5间接
RubyGemsrubocop-performance1.24.0间接
RubyGemsrubocop-rails2.30.3间接
RubyGemsrubocop-rake0.7.1间接
RubyGemsrubocop-rspec2.31.0间接
RubyGemsrubocop-rspec_rails2.29.1间接
RubyGemsruby-progressbar1.13.0间接
RubyGemssecurerandom0.4.1间接
RubyGemsthor1.3.2间接
RubyGemstimeout0.4.3间接
RubyGemstzinfo2.0.6间接
RubyGemsunicode-display_width3.1.4间接
RubyGemsunicode-emoji4.0.4间接
RubyGemsuri1.1.1间接
RubyGemsuseragent0.16.11间接
依赖安全公告 8

该仓库未发布可被索引解析的包,因此评估的是其自身的依赖图——共 78 个包,其中也包含从不交付的开发与测试版本固定:8 个存在已知公告,1 个为直接依赖。 有 2 个无法评估——没有已解析的版本、生态系统不受支持,或不在所列包清单之内。

软件包版本关系严重程度公告数修复版本
rack-session2.1.0间接严重22.1.2
activesupport8.0.2间接38.1.2.1
concurrent-ruby1.3.5间接31.3.7
nokogiri1.18.6间接131.19.4
rack3.1.12间接213.2.6
activerecord8.0.2直接18.0.2.1
net-imap0.5.6间接90.6.4.1
actionview8.0.2间接18.1.2.1

公告表示依赖图中记录的版本落入某条公告的受影响范围。可达性未经分析,且依赖图包含开发与测试的版本固定——某项发现可能只涉及工具链而非交付的软件。

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 1914,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "Ruby": 248958
      },
      "pushed_at": "2026-07-13T15:28:56Z",
      "created_at": "2021-08-18T14:13:01Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-14T03:45:47Z",
      "description": "a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process millions of background jobs per day",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "Ruby",
      "significant_languages": [
        "Ruby"
      ]
    },
    "owner": {
      "blog": "https://betterment.com",
      "name": "Betterment",
      "type": "Organization",
      "login": "Betterment",
      "company": null,
      "location": "New York, NY",
      "followers": 105,
      "avatar_url": "https://avatars.githubusercontent.com/u/871554?v=4",
      "created_at": "2011-06-23T18:42:51Z",
      "is_verified": null,
      "public_repos": 91,
      "account_age_days": 5507
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v3.1.0",
          "kind": "minor",
          "published_at": "2026-07-13T15:29:31Z"
        },
        {
          "tag": "v3.0.1",
          "kind": "patch",
          "published_at": "2026-06-15T15:23:19Z"
        },
        {
          "tag": "v3.0.0",
          "kind": "major",
          "published_at": "2026-06-08T17:13:25Z"
        },
        {
          "tag": "v2.2.0",
          "kind": "minor",
          "published_at": "2026-02-12T21:09:53Z"
        },
        {
          "tag": "v2.1.0",
          "kind": "minor",
          "published_at": "2026-02-05T16:54:20Z"
        },
        {
          "tag": "v2.0.3",
          "kind": "patch",
          "published_at": "2026-02-02T15:52:05Z"
        },
        {
          "tag": "v2.0.2",
          "kind": "patch",
          "published_at": "2026-01-05T22:09:33Z"
        },
        {
          "tag": "v2.0.1",
          "kind": "patch",
          "published_at": "2026-01-05T19:58:15Z"
        },
        {
          "tag": "v2.0.0",
          "kind": "major",
          "published_at": "2025-12-19T22:16:50Z"
        },
        {
          "tag": "v1.2.1",
          "kind": "patch",
          "published_at": "2025-10-06T19:23:42Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2025-10-03T14:16:17Z"
        },
        {
          "tag": "v1.1.0",
          "kind": "minor",
          "published_at": "2025-09-12T21:13:58Z"
        },
        {
          "tag": "v1.0.0",
          "kind": "major",
          "published_at": "2025-05-08T20:17:13Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2025-04-03T17:26:12Z"
        },
        {
          "tag": "v0.7.2",
          "kind": "patch",
          "published_at": "2025-04-02T22:22:27Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2025-01-24T22:11:09Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2025-01-24T22:11:00Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2024-12-18T18:14:35Z"
        },
        {
          "tag": "v0.5.5",
          "kind": "patch",
          "published_at": "2024-08-13T21:59:24Z"
        },
        {
          "tag": "v0.5.4",
          "kind": "patch",
          "published_at": "2024-05-01T01:03:51Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2023-01-20T22:07:16Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2021-11-30T23:57:57Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2021-10-27T16:09:21Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2021-09-02T16:48:15Z"
        },
        {
          "tag": "v0.1.1",
          "kind": "patch",
          "published_at": "2021-08-19T20:35:00Z"
        },
        {
          "tag": "v0.1.0",
          "kind": "minor",
          "published_at": "2021-08-19T20:34:26Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "c726753839bef4bec1b4d5dbe9945cb43d487edc",
          "body": "## What\n\nReplaces `method_missing` dispatch for named priority lookups with real\nmethod definitions:\n\n- **Class methods** — `Delayed::Priority.eventual`, `.interactive`, etc.\n- **Instance predicates** — `priority.eventual?`,\n`priority.interactive?`, etc.\n\nMethods are defined when the class loads (fo\n[…]\nn safety.\n- Full suite run locally on sqlite across multiple seeds; only failures\nare pre-existing on `main` in my local environment (Rails 8.0.2\nsnapshot/error-class drift, unrelated to this change).",
          "is_bot": false,
          "headline": "Define named priority methods as real methods (#115)",
          "author_name": "Corey Farwell",
          "author_login": "frewsxcv",
          "committed_at": "2026-07-13T15:28:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a5375ed749a0fb318cfd3abf0d6b1b65231f5698",
          "body": "This pr https://github.com/Betterment/delayed/pull/96 made a change to\nhow max_age is calculated, where it started including the run_at's of\njobs that were currently locked. This made our max_age monitor go off\neven when we had a single long-running job in the queue, instead of\nshowing us when the queue was backed up.\n\nThis restores the previous behavior where max_age only includes jobs\nthat are pending, and not actively being worked off.",
          "is_bot": false,
          "headline": "fix: max_age metric excludes locked jobs (#114)",
          "author_name": "Harris Effron",
          "author_login": "effron",
          "committed_at": "2026-06-15T14:48:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dbaf6a22f574f038ffa78b7220933af634890f3d",
          "body": "…ds without going through the adapter (#113)\n\nIntroduces new assertion that enforces that an ActiveJob cannot be\npassed into DJ enqueue methods, indicating to consumers that\n`perform_later` should be used instead in order to pipe through the\nadapter.",
          "is_bot": false,
          "headline": "fix: Assert ActiveJobs can not be passed directly to DJ enqueue metho…",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-06-02T19:48:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "564af1120a4b2b9a155377ebdbce9c468ffed1c1",
          "body": "- Fixes README to properly reflect the `enqueue` lifecycle event\n- Refactors unnecessary method nesting in the AJ adapter.",
          "is_bot": false,
          "headline": "fix: README to reflect accurate enqueue lifecycle shape (#112)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-06-02T15:41:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "42e5eb9a54caea9ad1f4eb2ca9dd3f4071e90576",
          "body": "Adjusts the ActiveJob adapter to pipe all enqueues through enqueue_all.\nResult of convo here:\nhttps://github.com/Betterment/delayed/pull/103#discussion_r3335987931\n\nNote for release: This is a mildly breaking change. Previous to delayed\n3.0.0, a `provider_job_id` was _always_ attached when enqueuing jobs. In\ndelayed 3.0.0, `provider_job_id` will only be attached depending on your\ndatabase configuration(specifically\n`ActiveRecord::Base.connection.supports_insert_returning?` must be true)",
          "is_bot": false,
          "headline": "fix: switch aj adapter to pipe through enqueue_all (#111)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-06-01T20:39:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5023edb74eb12e50bb4bbef1280b850e06651f5a",
          "body": "Implements `enqueue_all` in order to support Rails 7.1 perform_all_later\n\nKey decisions:\n- Maintain safety against `enqueue_after_transaction_commit`\n- Adjusts shape of `enqueue` lifecycle hook\n- Keep enqueueing logic isolated to `Delayed::Job` so that `Delayed::ActiveJobAdapter` can stay pure to job setup and post-enqueue object manipulation.\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: Implement enqueue_all (#103)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-06-01T17:56:40Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "1f22eb506244bde11f4f2a7e8d69964133a1b675",
          "body": "Replacement for https://github.com/Betterment/delayed/pull/107 since I\nforgot to fork the repo before developing.",
          "is_bot": false,
          "headline": "refactor: Drop support for Delayed::Worker.delay_jobs being proc (#108)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-05-21T17:10:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0d920e736e5f0281f8736908cbedfb5fe93ff065",
          "body": "Remove the job.hook(:enqueue) invocation from enqueue_job so payloads\ncan no longer mutate or observe the job at enqueue time. The hook method\nnow short-circuits on :enqueue with a deprecation warning for any direct\ncallers, and enqueue_job emits its own deprecation warning (naming the\npayload class\n[…]\ning the regression guard that ActiveJob JobWrapper payloads do not\ntrigger the deprecation warning.\n\ncc @effron @smudge\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: deprecate :enqueue job hook (#106)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-05-21T15:39:48Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4347124361f140ae2200b85ee55c14381141c78d",
          "body": "The before_save :set_default_run_at, :set_name callback is magical and doesn't actually prevent anyone from adding a new before_save that skips callbacks. Move the defaulting into Delayed::Backend::JobPreparer so both the single save path and any future bulk-insert path pick up defaults from the sam\n[…]\nun_at and name is now a DB-level NOT NULL violation -- callers should route through Delayed::Job.enqueue (which runs JobPreparer).\n\nBreaking change for anyone reaching past the documented enqueue API.",
          "is_bot": false,
          "headline": "refactor: Push run_at / name defaulting into JobPreparer (#104)",
          "author_name": "Caleb Donoho",
          "author_login": "CelticMajora",
          "committed_at": "2026-05-04T20:02:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "00d575c23b918cd79a174be7617c0847a40ab9cb",
          "body": "@medlefsen noticed that [ActiveJob states this in their\ndocs](https://apidock.com/rails/ActiveJob/Enqueuing/ClassMethods/perform_later)\nw.r.t. `perform_later`:\n\n> Returns an instance of the job class queued with arguments available\nin Job#arguments or false if the enqueue did not succeed.\n\nSince sil\n[…]\nlicit the current behavior, which\npresumably is specific to the AJ backend.\n\nThe `delayed` backend will not return `false` from `perform_later`, and\nwill instead bubble out an exception.\n\n/no-platform",
          "is_bot": false,
          "headline": "style: Add (passing) tests for regression coverage (#102)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-20T20:46:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "02864fbf8a1b8ecd2a46975add3330138ce72ce3",
          "body": "We previously [deprecated\nCHANGELOG.md](https://github.com/Betterment/delayed/commit/a8b6c9cd3aedc6f06e6e6421baa827aad7058c14#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed)\nin favor of GitHub release notes.\n\nThis updates the gemspec to properly reflect that change on sites that\nrender a link to the changelog (e.g. rubygems.org).",
          "is_bot": false,
          "headline": "fix: Update changelog_uri to GH releases (#101)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-13T16:53:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1c6c122d00c9e69b9058dcbca338494e0c9b04ae",
          "body": "This releases the latest series of monitor changes as v2.2.0\n\n/no-platform",
          "is_bot": false,
          "headline": "release: v2.2.0 (#98)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-12T21:01:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "af898a0f604f6d6ee8ee31fdb5ed7541907e099f",
          "body": "Given that `live_count_grouped` is already necessary for computing all\nthe other live queue metrics, we can reduce the work on MySQL by running\n`failed_count_grouped` (and filtering the scan down to fewer rows)\ninstead of an entire count against the table.",
          "is_bot": false,
          "headline": "refactor: De-complexify 'count' metric (#97)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-12T20:49:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d84ad27cbb0357c59e20f3bc169fc7d3f28b948e",
          "body": "This folds `max_lock_age` and `max_age` (and by extension,\n`age_alert_percent`) into the combined monitor queries introduced in the\nprior commit.\n\nAs before, I split these into two aggregate queries, hinging on whether\n`locked_at` (and therefore, a heap lookup) is required to return a\nresult.\n\n/no-platform",
          "is_bot": false,
          "headline": "perf(monitor): Fold max_* into combo queries (#96)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-12T20:46:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1e3f706c67d38f903e6dbb6737c1fc6766df6236",
          "body": "This combines queries for `count` (live), `future_count`, and\n`erroring_count` into a combined query for \"live\" jobs.\n\nIt also combines `locked_count`/`working_count` and `workable_count`\ninto a combined query for \"pending\" jobs.\n\nThe reason this is still two queries and not one is because on\nPostgr\n[…]\ncan and can\ntheoretically benefit from not having to hit the heap. (Though\nunfortunately it sometimes does require heap visits, depending on how\nrecently a given page has been VACUUMed.)\n\n/no-platform",
          "is_bot": false,
          "headline": "perf(monitor): 4 fewer queries w/ equivalent scans (#95)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-12T20:32:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2b6c0f63de9cf57e546b56d08ff51ca77921d349",
          "body": "After the change in #89 to have `delayed` surface DB-accurate max_age\nmetrics, it appears that PostgreSQL will occasionally report a `run_at`\nthat is milliseconds _after_ the current NOW() in UTC time.\n\nThis is more than just clock drift between the DB and the server that\ninserted the row -- in READ\n[…]\n\n- Switches PostgreSQL to `STATEMENT_TIMESTAMP()` to get even closer to\n  the time at which the `SELECT` actually began.\n- Clamps at 0 anyways because clock drift is still a possibility.\n\n/no-platform",
          "is_bot": false,
          "headline": "fix: clamp max_age metrics at 0 (#94)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-11T15:44:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d3bfb4cf6b99e7a886e2d2ac25c1eb095e229559",
          "body": "This is pretty self-explanatory -- the performance impact is probably\nde minimis, but the readability is certainly improved, IMO!\n\n/no-platform",
          "is_bot": false,
          "headline": "refactor: Simpler priority case statement (#93)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-11T14:50:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c74f8e037fdcad8df1f68fb885c2006699d75c88",
          "body": "This only affects the rspec snapshot output, but I was finding that the\ndiffs were getting pretty noisy and wanted to split these out more\ncleanly before making structural changes to the query.\n\n/no-platform",
          "is_bot": false,
          "headline": "style: break aliased columns into multiple lines (#92)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-10T20:30:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "79425210b3504bc9340469c938f024273d019e65",
          "body": "…past (#99)\n\nThis catches potential code bugs that accidentally enqueue jobs with\n`run_at` set in the past. Often time we set `run_at` explicitly to\nforward date jobs, but bugs can lead us to accidentally enqueuing jobs\nin the past, which can lead to unnecessary max_age alarms and other\nconfusing metrics about queue max_age.\n\nBy default `deny_stale_enqueues` is false as to not introduce a breaking\nchange.\n\n/no-platform",
          "is_bot": false,
          "headline": "feat: Add config to raise exceptions when run_at sufficiently in the …",
          "author_name": "Harris Effron",
          "author_login": "effron",
          "committed_at": "2026-02-10T20:03:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0966d03417610c035f47101cc48e751ceff1b8f4",
          "body": "This is a test-only change that improves the way that query snapshotting\nis done for the monitor process.\n\nPreviously, each metric was given its own example context, testing its\nqueries in isolation. However, in practice, the monitor does not run\nthis way, and it intentionally memoizes query results\n[…]\nnt test coverage!\n\nWith this PR, monitor queries are dumped out into a single snapshot,\ndelineated by a few text dividers for readability by humans (& better\nhinting for the git differ).\n\n/no-platform",
          "is_bot": false,
          "headline": "test: Rework monitor coverage to avoid dups (#91)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-10T18:41:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "685c4fb37af235e43ce3b1f40f9cf0a3ec0b72fa",
          "body": "By inserting more rows up front, MySQL will behave more\ndeterministically.\n\n(In practice, I was finding that 10 was slightly too few, and doing it\non every test query is unnecessary if a single example covers multiple\nqueries.)",
          "is_bot": false,
          "headline": "fix: avoid test flakes on mysql (#90)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-10T18:06:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "107962162c0b831da5091610894d88bd16a1a0f2",
          "body": "By default, Rails wants to disable eager loading inside of `rake`\ncommands, even if `eager_load` is set to true. This is done to speed up\nthe boot time of rake tasks that don't need the entire application\nloaded.\n\nThe problem is that long-lived processes like `delayed` **do** want to\neager load the \n[…]\nn,\nwhich tells us whether the app has already eager loaded in a `rake`\ncontext.\n- If `rake_eager_loading` is not defined or `false`, we will then check\n`cache_classes` & explicitly eager load if true.",
          "is_bot": false,
          "headline": "fix: Eager load when 'rake_eager_load = false' (#88)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-05T16:50:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c06c1978ef07a30534df4de1b911efc69f443000",
          "body": "This ensures that `max_lock_age` and `max_age` are not artificially\ninflated by slower query times. Previously, if the query took N seconds\nto perform, N seconds would be artificially added to `max_age` and\n`max_lock_age`. (Typically this is not a problem for sub-second queries\non small-medium table\n[…]\nction (as should the state of the rows it returns), and in other\nDB engines it will correspond to a clock time during execution, so there\nis still room for variance but it is much smaller than before.",
          "is_bot": false,
          "headline": "fix(monitor): DB-accurate max_age metrics (#89)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-05T16:49:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ac916129facf873ce77f7d38e9cd28f10495ef47",
          "body": "Say you have a jobs table with 1M rows, 4 named priority ranges, and 2\nqueues.\n\nRather than:\n- `GROUP BY (CASE ... END) AS priority, queue, COUNT(*)` (1M rows)\n\nThis does:\n- [outer] `GROUP BY (CASE ... END) AS priority, queue, SUM(count)` (~8\nrows)\n  - [inner] `GROUP BY priority, queue, COUNT(*)` (1\n[…]\nthis shave ~2-3 seconds off of each\n`delayed:monitor` query in a production table with 10M+ rows. For tables\nwith far fewer rows, the difference is negligible. (And, as always, your\nmileage may vary!)",
          "is_bot": false,
          "headline": "perf(monitor): reduce per-tuple work in hot path (#87)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-02-02T15:50:42Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "838c5a590ab48908d713d3b38f99c432576a5c52",
          "body": "…ation wait timeout) (#86)\n\nPreviously, `with_retry_loop` had wrapped the \"drop\" & \"add\" operations\nseparately. This is fine in the case of lock timeouts, as it will retry\nuntil it acquires a lock as expected.\n\nHowever, if `add_index` experiences a statement timeout (and we have not\nyet exceeded the\n[…]\neSQL, w/ the `:concurrently` opt) an invalid index was left\nbehind. The operation should first remove the invalid index before\nattempting another `add_index` call, and that's what this commit changes.",
          "is_bot": false,
          "headline": "fix: Allow statement timeouts on index creation to retry (within migr…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-01-05T22:07:34Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d8a44e12fd89266b8832351936ae805e9098c9a9",
          "body": "`@migration_start` was never getting set, and there wasn't any code exercising the non-happy path for these migrations.\n\nThis also bumps the version to 2.0.1",
          "is_bot": false,
          "headline": "fix: Ensure that migration lock timeout loop works as expected. (#85)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2026-01-05T19:57:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "62df90556503b0cd9a5d621d783037ae472eaed0",
          "body": "This releases `v2.0.0`, packed with improvements to `delayed`'s overall query performance and index coverage.\n\nNew indexes can be installed with `bundle exec rake delayed:install:migrations` (please review & test any migrations in your own application before shipping them!)",
          "is_bot": false,
          "headline": "build: Ship 2.0.0! (#71)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-19T21:49:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f7c87754abaf9cb7789d05d7401c1f23a853a1fa",
          "body": "…#82)\n\nThis does two things:\n1. Avoids querying both `locked_count` and `working_count` separately (since they are synonymous)\n2. Splits the (total) `count` metric into two: `failed_count` and `live_count`\n\nWorth noting that when a scan covers the entire table, **a sequential scan might actually be \n[…]\nurthermore, in postgres at least, these don't just become index scans -- they become _INDEX ONLY_ scans, which should be cheaper than table scans since the index should be significantly more compact!)",
          "is_bot": false,
          "headline": "perf(monitor): avoid querying the same thing twice, avoid seq scans (…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-11T18:18:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8413694b5a2e7d8fc0fe76a1341471c96872b5c9",
          "body": "… index (#84)\n\nThis PR aims to provide reasonable unit-level assurance that new code can be rolled out _before_ new indexes are applied.\n\nIt applies the same snapshotting strategy to all notable query plans, but generates the plans against a schema with the legacy index (and none of the new indexes)\n[…]\nt structurally the same (if not better). **This does not account for `cost` changes,** but should give us a good signal to ensure that we don't regress an `Index Scan` into a `Seq Scan` (for example).",
          "is_bot": false,
          "headline": "test(migration path): ensure that new queries are compatible with old…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-11T17:06:13Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "07adf00c9e85bab79101fcda31f28f0981f8b17f",
          "body": "…ing & valid index (#83)\n\nThis allows for proactively adding indexes out of band, so that the migrator will no-op and complete quickly. For very large tables this may be necessary to stay within constraints of synchronous deployment steps (e.g. a timeout on migration processes).\n\nI also adjusted test logger setup so that it relies entirely on log levels, and will log out migration warnings even if debug logging is turned off for the rest of the test suite.",
          "is_bot": false,
          "headline": "fix: Improve `upsert_index` to only drop+rebuild if there is no match…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-10T20:52:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc726776815c94954c8b0208fa4388989f28fec4",
          "body": "When using `ActiveRecord.default_timezone = :local` and a local timezone that has a daylight savings \"fall back\" hour, it was possible for future-scheduled jobs to be immediately claimable, producing a \"spinloop\" of retries until the job achieves exit velocity.\n\nThis results from the fact that the D\n[…]\n PR is that **future-scheduled jobs will now be deferred to immediately _after_ the \"fall back\" hour.**\n\n(Jobs that do not specify a `run_at` are not affected and will still be claimable immediately.)",
          "is_bot": false,
          "headline": "fix: prevent DST + `:local`  from breaking job backoff (#81)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-09T20:50:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b0a48ace98b85d998c473f4df67f7ad09a1048ae",
          "body": "(...which also includes `run_at` even though it's not in the name.)\n\nI'm relying on tests to prove that nothing this framework does relies _strictly_ on this index anymore. As such, none of the snapshotted query plans have changed! 🎉 \n\n**(Naturally, consumers of this gem can choose to keep any indexes they find useful.)**",
          "is_bot": false,
          "headline": "perf: remove legacy/unused \"delayed_jobs_priority\" index (#78)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-09T15:31:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a3df1e744cce7c1564ad770df239c5de2819cb5",
          "body": "This adjusts pickup queries so that `run_at <= ?` can be included as an `Index Cond` (rather than as a `Filter`).\n\nIt also improves a few monitor queries in minor ways that signifcantly improve the `cost` (not visible in our snapshots, but observed by me in a live testing context).\n\nThe core conceit\n[…]\nd `workable_by` to `claimable_by`\n\nI did **not** adjust any `Delayed::Monitor` metric names. Several of these are already outdated and will merit a migration path, but for now I'm not addressing them.",
          "is_bot": false,
          "headline": "perf: Improve selectivity of pickup query / \"claimed\" states (#80)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-09T15:31:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8a6dcb156f6ff3832d10b84d117de28f7a166719",
          "body": "As I've been testing this series of query/index changes, I noticed that things would hang everytime a new deploy went out. This is because when workers shut down, they perform a cleanup step to clear the locked state for any jobs that they had previously locked (and failed to complete, for whatever \n[…]\n (resulting in a full sequential scan) as there is no index that targets this column. By scoping the update to the `claimed_by(worker)` scope, we can turn it into an index scan against only live rows.",
          "is_bot": false,
          "headline": "perf(worker): avoid sequential scan during worker shutdown (#79)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-08T16:19:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "443edb0417810f07551d5113dd60bfa161911a65",
          "body": "…. (#77)\n\nOn PostgreSQL, we already do not index `locked_at` or `locked_by` to optimize for [HOT updates](https://www.postgresql.org/docs/current/storage-hot.html) during job pickup. However, this does not benefit other DBs that don't follow the same MVCC/page architecture.\n\nThis PR:\n- Indexes `lock\n[…]\nker can run it (one to insert the row, one to lock the row). But 50% is the upper bound, so we went with 33% instead to further reduce the odds of page thrashing when picking up multiple jobs at once.",
          "is_bot": false,
          "headline": "perf: HOT updates for PostgreSQL, index `locked_at` for everyone else…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-05T19:28:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "575f92a8272b60edcfcce9f6783988e2163f7231",
          "body": "Postgres and Mysql both indicate that this enables them to use an index-only/covering scan for the `erroring` scope/metric 👍 \n\nIt would be perfectly possible to index on `last_error`, but indexing an `integer` column is a lot more straightforward than the strategies we'd need to employ for a `text` \n[…]\ng. we could index on `WHERE last_error IS NOT NULL` for DBs that support indexes on expressions, but we could also do that with `WHERE attempts > 0` just as effectively, so I'm going with `attempts`.)",
          "is_bot": false,
          "headline": "perf(monitor): use 'attempts > 0' for better indexability (#76)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-05T13:53:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b01d8fc9e896686e0b14fa0f489b6edd3cea11f1",
          "body": "Per [this](https://github.com/Betterment/delayed/pull/74#discussion_r2586219398) comment, I've included `WHERE locked_at >= ?` as part of the `claimed_by` scope, making it \"timeout aware\" rather than returning records that have long since had their claim expired.\n\nIn practice, this will have minimal impact on both correctness (slightly better but in the more unlikely cases) & performance (same or a hair worse), so it's more about avoiding unnecessary discrepancies than anything else.",
          "is_bot": false,
          "headline": "fix: Include 'claimed' as part of 'claimed_by' scope (#75)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-04T19:20:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "12148a75ea16a517df4609d7dc35716fd5ca8638",
          "body": "… (#74)\n\nSince we already don't index `locked_at` (optimizing for [HOT updates](https://www.postgresql.org/docs/current/storage-hot.html) in PG), this only impacts existing `Filter:` plans.\n\nI believe date comparisons are more expensive than `NOT NULL` checks, but the minor performance difference sh\n[…]\n for some reason we observe this change causing issues, an alternative would be to forego HOT updates and index `locked_at`. (I am already prepping a PR that indexes `locked_at` for non-PG databases.)",
          "is_bot": false,
          "headline": "fix(monitor): Obey lock timeout when reporting claimed/unclaimed rows…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-03T22:26:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e31fe6ac8533824c528f46fd5f6da183de75e7b6",
          "body": "…hem (#73)\n\nThis fixes a longstanding bug in the monitor queries that caused `future_count`, `locked_count`, and `erroring_count` to include failed jobs.\n\n(The latter sort of comes down to semantics, but \"erroring\" to me implies that it has not yet terminally failed. I also checked and it was out of\n[…]\nt stuck at a nonzero baseline.\n\n💪 Conveniently, this also improves the query plans for all three fields. (`future_count` even becomes an `Index Only` (postgres) / `Covering index` (mysql) scan/lookup!",
          "is_bot": false,
          "headline": "fix(monitor): exclude failed jobs from metrics that shouldn't count t…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-03T18:26:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "bbd6815dfeb17ef0a65eb0bea6b8f4e313f8dac7",
          "body": "/no-platform\n\nThis also makes `AddIndexToDelayedJobsName` make use of it, and fixes a\nminor spec bug I found with Rails 6.0 appraisals where `drop_table` was\nnot getting run (because `:if_exists` wasn't added until 6.1)",
          "is_bot": false,
          "headline": "fix: Ensure that upsert_index is reversible (#72)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-03T17:27:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "17be23f401e0447611f56346de4a065a251ec7d1",
          "body": "This PR is the first of several query optimizations, and can largely be read in two parts:\n\n1. A pair of heavily-annotated migrations that add new index(es) as safely as I could muster generically (w/ timeouts and a lock retry loop). I also updated the `README` w/ some expanded instructions and ⚠️ s\n[…]\n `Table scan` to `Covering index range scan`\n        - `max_lock_age`, `max_age`, `working_count`, `workable_count`,  and `alert_age_percent` go from `Table scan` to `Index lookup` w/ index conditions",
          "is_bot": false,
          "headline": "feat(perf): New indexes for job pickup & monitoring (#70)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-02T18:53:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "dafa66ec0ca4ed42ab46a42bae135ac8e6d06842",
          "body": "This adds adapter-specific EXPLAIN outputs to the snapshot files introduced in #67.\n\nThe goal is to test that available indexes are usable, and enable us to flag changes/regressions in query plans going forward. **The goal is NOT to test or predict performance.** These are essentially \"unit\" tests f\n[…]\nstable to me, so I wasn't finding a need to attempt to trick it into doing anything different.\n\nThis depends on #68 being merged first (mostly because I want to use mysql's new \"TREE\" plan formatter).",
          "is_bot": false,
          "headline": "test: Snapshot test all EXPLAIN query plans (#69)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-01T21:35:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "710c9f9f1d8c1f4c461d20012c45f47f1e853c42",
          "body": "I'd like to get this change to our workflow merged so that subsequent\nPRs can run their tests w/ known-passing versions of their DB\ndependencies. As we are starting to more directly cover the raw adapter\noutputs, this will help us prevent CI from randomly breaking on its own.\n\n/no-platform",
          "is_bot": false,
          "headline": "test: Normalize DB versions used in CI (#68)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-01T20:15:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0c45793f4ff330264b3f3b44d741bf888cbbfac2",
          "body": "I'm introducing @rzane's `snapshot_testing` as a means to:\n- Make iterating on the queries a lot easier. (Removes all the manual work required in updating spec files, but still flags notable SQL differences in PRs so they can be reviewed before they ship.)\n- Removes the need to strip/normalize delim\n[…]\n instead of two now.\n\nThis might feel like a big diff, but it's really just capturing what's already there, and I very much intend to make use of this pattern to make several changes more confidently.",
          "is_bot": false,
          "headline": "test: Use snapshots for easier SQL testing/iteration. (#67)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-01T19:42:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "820ca77b7f3b15f1b8929617e7037512dfa8e1d3",
          "body": "…Error (#66)\n\nThis fixes a regression flagged by #64. It was originally addressed in\nddd7a1c288b6425cac320e3de410d2dea51fbb86 but got reverted by [a linter\nauto-correct](https://github.com/Betterment/delayed/commit/78bd49e75638bc40c6c437d2848bd5dc31767ad0)\n🤦.\n\nThis reverts the revert, and adds a regression test that proves that\n`rescue StandardError` does not inadvertently catch\n`Delayed::WorkerTimeout`, allowing it to bubble out and be handled by\nthe worker.",
          "is_bot": false,
          "headline": "fix: max_run_time timeout error should not be rescuable as a Standard…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-01T19:07:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4813b1a7501639d2b353691c38e7d7e8e5bf0a39",
          "body": "There are other ways to slice this, but because of the threaded nature\nof the job runners, `Timecop` wasn't cutting it. This gives us ~1.5\nseconds of tolerance rather than ~1 and seems to have fixed my local\nreproduction of the flake (running tests in a loop for a long time).",
          "is_bot": false,
          "headline": "fix(tests): timing issue on test flake (#65)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-12-01T18:42:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ed8b6cebbdf4ee167aeb9bd0b8499895399f030e",
          "body": "The goal is to create some unifying structure around the way that scopes\nare defined and organized in the ActiveRecord `Job` class, without\nchanging the actual SQL in any meaningful way. (This sets me up to make\nmeaningful SQL changes in future PRs!)\n\nIn addition to using `Arel` for `gt`/`lt`/`gteq`\n[…]\n `pending` (where run_at <= now)\n\nAgain, this should **not** meaningfully change any SQL! Only minor\nformatting changes without changing the meaning of each query. (I'm\nusing the tests to prove this.)",
          "is_bot": false,
          "headline": "refactor: Remove 'ready_scope' and improve remaining scopes. (#63)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-11-24T22:46:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f8dad798e68e6fd30d23f750434799416a02381f",
          "body": "…ss-wide config (#62)\n\nIdeally `max_run_time` is the same across an entire worker pool, because\notherwise workers might pick up jobs that are still held by other\nworkers in the pool.\n\nSo it has never made sense (based on our current pickup/monitor logic)\nthat this would be configurable on a per-worker-instance or per-query\nbasis.",
          "is_bot": false,
          "headline": "refactor: Add 'lock_timeout' & clarify that 'max_run_time' is a proce…",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-11-21T21:07:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "16c000c047df34018deb0a70cfc5a20c24d3f24d",
          "body": "This is the first in a series of PRs where I will aim to rework &\noptimize this gem's query logic. I ultimately aim to add new index(es),\nand generally improve the indexability & runtime performance of these\nqueries, and will produce a new major release at that point.\n\n👉 But step one is to **test th\n[…]\nrbose, but I argue that if the\nlibrary is capable of producing N distinct queries, we should have N\ntests, and look for ways to reduce the number of queries (rather than\nmaking the tests more clever).",
          "is_bot": false,
          "headline": "test: Add \"golden\" tests for worker/monitor SQL (#61)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-11-20T22:29:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "33a485b2063645f91d8951970d6f2d39b5cc115d",
          "body": "This updates the method & tests to properly ensure that the column value\ngets assigned before_save.\n\nReason: The `||=` operator results in no assignment, because `name` is\noverridden for backwards-compatibility if the column does not exist.",
          "is_bot": false,
          "headline": "fix: Fix set_name callback, and improve spec coverage (#60)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-10-06T19:18:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8a3590393e3b668dec1627a2bed633a67a7f8cef",
          "body": "This adds a `delayed_jobs.name` column that will be populated by default\nwhen each job is enqueued. While this column starts as nullable (to\nensure zero downtime is required to make this change), it will be\npromoted in a future release to an expected column. **Update: this\nfuture release will be TBD\n[…]\nin the table, etc)\n\nThis is the first real schema change we've made, and as such I had to\nrewire a little bit of plumbing to make it easier to drop a new\nmigration into an existing app (see `README`).",
          "is_bot": false,
          "headline": "feat: add `delayed_jobs.name` column (#59)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-10-03T14:10:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d46a0fb3a4032af549d5226fa57c8ebe51665827",
          "body": "**Summary of changes:**\nThis pull request drops support for the oldest Rails and Ruby versions,\nin order to streamline the supportability bracket. By doing so, It also\ncuts a minor version release.",
          "is_bot": false,
          "headline": "Drop support for Rails 5.2 & Ruby 2.7 (#58)",
          "author_name": "Irving C.",
          "author_login": "Irving-Betterment",
          "committed_at": "2025-09-12T21:12:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "397dbe3176ddfd280be7b945e57f7725173f6cb8",
          "body": "This PR includes automated changes from running\n[frizbee](https://github.com/stacklok/frizbee) to pin Github Actions.\nFor more information on what this actually means, take a look at [our\ndocumentation on\nUnpinnedActions](https://github.com/betterment/claws?tab=readme-ov-file#unpinnedactions).\nBecau\n[…]\ncd6fda/.github/workflows/claws_fork_friendly.yml#L50-L54))\n\nFor other findings and how to remediate them, check the [Claws\ndocs!](https://github.com/betterment/claws?tab=readme-ov-file#built-in-rules)",
          "is_bot": false,
          "headline": "chore: Pin Github Actions to Commit Hashes (#56)",
          "author_name": "Omar A.",
          "author_login": "6f6d6172",
          "committed_at": "2025-07-01T22:24:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e5a1123414ec3265ff13fcc3e2fd164648813e7",
          "body": "This PR bumps us to `1.0.0` because in cleaning up our lifecycle hooks,\nI made a breaking change to remove the `job` argument from the `:thread`\nlifecycle callback. Coupling threads & jobs 1:1 was an implementation\ndecision, and we are restricting ourselves by baking that decision into\nthe callback \n[…]\nattempting to work\noff the same jobs over & over. (Even with `locked_at`, these workers can\nslowly degrade if enough jobs are consistently stuck in a locked state\npast the lock timeout.)\n\n/no-platform",
          "is_bot": false,
          "headline": "Delayed 1.0: Lifecycle & job cleanup improvements (#55)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-05-08T20:15:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "427bc304715961e5c56c3b08d90bfc6a43b0d87b",
          "body": "I'm adding regression coverage for the changes made in #53, and making\none additional change in line with the way that the `:perform` callback\n[works in the `delayed_job`\ngem](https://github.com/collectiveidea/delayed_job/blob/ea4879dd3c2f3f5aa7f616a6f0fd58f2bbc2a481/lib/delayed/worker.rb#L313)\n- th\n[…]\nast\npriority to a float, which was raising a method undefined error. So I\nadded `Delayed::Priority#to_f` and added a test to cover all the\nexplicit conversion methods (`to_s`, `to_i`, `to_f`, `to_d`).",
          "is_bot": false,
          "headline": "0.8.0 - `Priority#to_f`, plus some regression coverage (#54)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-04-03T17:24:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2ea5f07954a89242d74af3b0990d3b1f0281b0df",
          "body": "/no-platform\n/domain @argvniyx-enroute @mavenraven \n\nPR https://github.com/Betterment/delayed/pull/42 inadvertently flipped the order of `:thread` and `:perform`, and also pushed `:thread` far enough in that cleanup steps would happen after the `with_connection`'s`end` block in the connection plugin.\n\nThis introduces the possibility of thread safety issues, or connections being held longer than intended (exhausting the connection pool / connection limits).",
          "is_bot": false,
          "headline": "[fix] Thread callback ordering (#53)",
          "author_name": "Nathan G",
          "author_login": "smudge",
          "committed_at": "2025-04-02T22:20:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b5abf39a0b8fcc23c31534f85c65f14f4b1437e6",
          "body": "### Description\r\n\r\nUpgrade betterlint 1.18.0 dependencies and regenerate RuboCop todo.",
          "is_bot": false,
          "headline": "Upgrade betterlint to 1.18.0 (#52)",
          "author_name": "Dan Kubb",
          "author_login": "dkubb",
          "committed_at": "2025-02-03T18:35:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f0fcc71fac3ed3adecccdd917a41f769cb6f8460",
          "body": "I configured deprecations to raise exceptions.\r\n\r\nOnce I did that, I had to suppress deprecations from `enqueue_after_transaction_commit = :always` and `enqueue_after_transaction_commit = :never`.\r\n\r\nThose values are still supported, but deprecated. Rails 8.0 now expects `true` or `false`. I've updated delayed to ensure that `enqueue_after_transaction_commit` is not set to `true`.",
          "is_bot": false,
          "headline": "Fix Rails 8.0 deprecation (#51)",
          "author_name": "Ray Zane",
          "author_login": "rzane",
          "committed_at": "2025-01-24T21:10:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7e184ba7f8207e78b6ac9be3bc3893b3bf5a2072",
          "body": "Adding 3.4 is a bit more complicated, because no versions of Nokogiri\r\nsupport 3.0 and 3.4. We'd need separate gemfiles, ro we'd need to only\r\ntest Ruby 3.4 against specific gemfiles.",
          "is_bot": false,
          "headline": "Add Ruby 3.3 to the CI matrix (#50)",
          "author_name": "Ray Zane",
          "author_login": "rzane",
          "committed_at": "2025-01-22T16:17:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a8b6c9cd3aedc6f06e6e6421baa827aad7058c14",
          "body": "This PR runs our test suite against Rails 8 on CI.\r\n\r\nThere was only one major change. `ActiveSupport::ProxyObject` was\r\ndeprecated in 7.2 and removed in 8.0.",
          "is_bot": false,
          "headline": "Rails 8 support (#49)",
          "author_name": "Ray Zane",
          "author_login": "rzane",
          "committed_at": "2025-01-15T16:01:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c8ac357f005081cad6076c605c7c81c6db03c055",
          "body": "… for now) (#48)\n\nThis is related to #41 and #42 insofar as a kind of DB-bound \"spinloop\"\r\nis still possible if a worker picks up jobs that take so little time\r\nthat the worker immediately turns around and asks for more. As of now,\r\nthere has been no way to tune the amount of time a worker should wa\n[…]\nracted from `min_reserve_interval` when\r\nthe worker sleeps, and if jobs take more than `min_reserve_interval` to\r\ncomplete than the worker will not sleep before the next reserve query.\r\n\r\n/no-platform",
          "is_bot": false,
          "headline": "Add a config for enforcing a minimum job reserve interval (default: 0…",
          "author_name": "smudge",
          "author_login": "smudge",
          "committed_at": "2024-12-18T18:12:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c0c36c0bed9fc62ab0cb0790e786ba9e4e6ae9ed",
          "body": "/no-platform\r\n\r\nmore updates @smudge",
          "is_bot": false,
          "headline": "update rails deps to fix security alerts (#45)",
          "author_name": "Sam Moore",
          "author_login": "samandmoore",
          "committed_at": "2024-08-20T14:25:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a352b1dec688fa9025ca1cb4532a9c2f73a53609",
          "body": "just what it says. we've got some dependabot alerts for these deps, so\r\nupgrading to fix that.",
          "is_bot": false,
          "headline": "update rexml + nokogiri to fix security alerts (#44)",
          "author_name": "Sam Moore",
          "author_login": "samandmoore",
          "committed_at": "2024-08-20T14:16:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "37dff237d53040abd6984990942f5c27f05db693",
          "body": "…or Rails 7.2 support (#43)\n\nHi Betterment friends!\r\n\r\nAs addressed in @smudge's PR\r\nhttps://github.com/Betterment/delayed/pull/40, Rails 7.2 introduces the\r\nconcept of `enqueue_after_transaction_commit`, which should not be used\r\nin conjunction with Delayed. The implementation, and the associated\r\n\n[…]\n13a4899c9f996ae51f54cdb8fa5f79f50efab872f9aR20)\r\nwhere `!!:never == true` and causes the condition to be true (and\r\nraising the exception) when `job.class.enqueue_after_transaction_commit\r\n== :never`.",
          "is_bot": false,
          "headline": "Handle proper values for ActiveJob.enqueue_after_transaction_commit f…",
          "author_name": "Chris Zega",
          "author_login": "warmerzega",
          "committed_at": "2024-08-13T21:55:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fd97a38875cf3eaa2081972f2bbf4405bffc09af",
          "body": "This ensures that exceptions raised in thread callback hooks are rescued\r\nand properly mark jobs as failed.\r\n\r\nThis is also a good opportunity to change the `num` argument (of\r\n`work_off(num)`) to mean number of jobs (give or take a few due to\r\n`max_claims`), not number of iterations. Previously (be\n[…]\n we only\r\nincrement `success` and treat `total - success` as the \"failure\" number\r\nwhen we return from the method.\r\n\r\nFixes #23 and #41\r\n\r\nThis is also a prereq for a resolution I'm cooking up for #36",
          "is_bot": false,
          "headline": "Fix spin-loop/cleanup failure mode within run loop (#42)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2024-06-27T16:35:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "24b75f56c43e470ecdec2d3afdcc11bb21a6e2e3",
          "body": "### Summary\r\nThis PR Adds a configuration value (`.assign_at_midpoint`) to `Delayed::Priority` \r\n\r\nThe intent is to allow users to change the default priority of a job (i.e. when using a named priority) to be the middle of the configured priority ranges -- derived from the default or custom priority\n[…]\n\n\r\n**Note**: since the last priority uses a range that is infinite, it's not possible to derive the midpoint. So, for now, I've defaulted to adding 5, since that matches our defaulted priority values.",
          "is_bot": false,
          "headline": "Allow configuring default priority value at midpoint of range (#39)",
          "author_name": "Terrance Koar",
          "author_login": "tkoar",
          "committed_at": "2024-04-29T17:16:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9a328c6e0518bbd141980653140379a12d6f8c98",
          "body": "…commit) (#40)\n\nThis anticipates the release of a new feature in Rails 7.2 introduced here: https://github.com/rails/rails/pull/51426\r\n\r\nIn Rails 7.2, ActiveJob will default `enqueue_after_transaction_commit` to `true`, and we would like to explicitly set this to `false` (retaining transactional enq\n[…]\n I also:\r\n- Added a new Appraisal for the latest Rails main build on GitHub.\r\n- Re-generated the rubocop todo file. There are changes we can pull out & autocorrect, but we can do them in separate PRs.",
          "is_bot": false,
          "headline": "Prepare for Rails 7.2 support (and disable enqueue_after_transaction_…",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2024-04-29T17:15:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "77b97e9f540f0cce849de4c9665f9963d3a15c47",
          "body": "We commit the lockfiles now, so version changes need a re-bundle. (I\r\nmissed this in the last PR.) Once this is in I can cut a new release.",
          "is_bot": false,
          "headline": "Fix the lockfile / linter build step (#38)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2024-01-31T03:45:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f2e5edc5d3f9481a9636d967d5cbe7ac191995eb",
          "body": "This PR introduces a change to avoid calling `ActiveJob::Base#deserialize` during queueing the job whilst maintaining existing functionality.\r\n\r\nThe change is in `JobWrapper`, which previously only took in a hash of `job_data` and knew how to deserialize it into a job object so we can interrogate it\n[…]\nialize` during the enqueuing, even those adapters that support invoking methods on the Job class instance like delayed does.\r\n\r\nFrom one angle it's an optimization, from another angle it's a bugfix (:",
          "is_bot": false,
          "headline": "Optimize JobWrapper queueing ActiveJobs (#35)",
          "author_name": "Caius Durling",
          "author_login": "caius",
          "committed_at": "2024-01-31T03:38:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "869c08a893b97d8fad51b2cc5c08e8e151c74b1b",
          "body": "Committing the lockfile triggered a bunch of dependabot errors, and I\r\nrealized that the versions committed were very out of date, so this\r\nupdates the lockfile with newer versions.\r\n\r\n/no-platform",
          "is_bot": false,
          "headline": "Address dependabot alerts (#34)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2024-01-08T18:23:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "737f2fd4eaa149758bce30da299e07a09489c680",
          "body": "**Summary of changes:**\r\nCommit lockfile previously ignored (see\r\nhttps://bundler.io/guides/faq.html#using-gemfiles-inside-gems).\r\n\r\n/task https://app.asana.com/0/0/1206244989881229/f\r\n\r\n---------\r\n\r\nCo-authored-by: Irving Caro <112433591+betterment-coding-agent@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Add lockfile (#32)",
          "author_name": "Irving Caro",
          "author_login": "Irving-Betterment",
          "committed_at": "2024-01-08T17:58:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "342acb982a575ecfbd5693bddbb1feab96b56589",
          "body": "This fixes #29, which can be reproduced on new Rails 7.1.1 apps in\r\ndevelopment.\r\n\r\nThe issue stems from the fact that Rails no longer loads ActiveJob at\r\nall until the first `ActiveJob::Base` class is referenced, but we\r\ndefined our `Delayed::JobWrapper` class in a file that only loads\r\n_after_ Act\n[…]\napper` out into its own file in `lib/`\r\nand load it when `lib/delayed.rb` loads. The class itself has no\r\ndependencies outside of `activesupport` (`delegate_missing_to`) so this\r\nis safe enough to do.",
          "is_bot": false,
          "headline": "Fix Rails 7.1 regression: Delayed::JobWrapper not defined (#30)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2023-10-19T18:26:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "429e20f0baddb1205d5ee7be7082e2a4afcd1f0e",
          "body": "This adds Rails 7.1, Ruby 3.1, and Ruby 3.2 to the build matrix.\r\n\r\nThe gem already works with these versions, but now they are formally\r\ntested. (As such, I bumped the gem by a patch version, to signify that\r\nthis is the first verified-compatible release.)",
          "is_bot": false,
          "headline": "Rails 7.1, Ruby 3.1, and Ruby 3.2 (#28)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2023-10-11T16:10:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "66125d123cff8742e729503dfdd1cc2dac7bb34a",
          "body": "…inconsistency (#24)\n\nThe issue here is that some ActiveJob-enqueued jobs would include the `@job` ivar in their serialized handler, which is redundant with `@job_data` and only existed for memoization purposes. We can use Ruby's `encode_with` API to ensure that we only encode `@job_data` into the h\n[…]\nsubsequent retries) occur. `DeserializationError` is really supposed to be only for cases where we fundamentally can't even attempt the job and allow the normal pattern of exception-handling to occur.",
          "is_bot": false,
          "headline": "Reduce handler size in some ActiveJob cases, and fix deserialization …",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2023-01-20T22:02:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f7a27c8da949ec73f0fc0fe06182788544f497fc",
          "body": "There are a couple linter rules complaining. This will ensure that anyone who branches off of main won't have to fix them independently.\r\n\r\nThis also fixes the Ruby 3.0 build so that it doesn't inadvertently run with Ruby 3.1 in CI.",
          "is_bot": false,
          "headline": "Fix main builds: appease the linter (#14)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2022-02-11T20:27:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "abad99fb2562a22e7d924c597b235dc4c74f1ae9",
          "body": null,
          "is_bot": false,
          "headline": "Move appraisal onto official Rails 7.0.0 release (#9)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-12-16T03:43:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6d9d831a731f20595f99655c3ae5436003caaeca",
          "body": "Resolves #6. While we already fully support Ruby 3.0 kwargs via ActiveJob, the legacy `delay` and `handle_asynchronously` APIs did not support the new [separation of positional and keyword arguments](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-\n[…]\npt keyword arguments. It should also support jobs that were enqueued by the prior `delayed` gem version (where the new `kwargs` accessor would be nil, and `args` contains the kwargs as its last item).",
          "is_bot": false,
          "headline": "Support Ruby 3.0 kwarg-handling within `.delay` API (#7)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-11-30T21:30:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2d296b9f49c24c99b91d8bf78690a20436811bae",
          "body": "This:\r\n- Adds an `Appraisal` entry for `7.0.0.alpha2`. (I had to specify multiple gems to get the pre-release dependencies to resolve properly.)\r\n- Bumps `.ruby-version` to `2.7`, but keeps linting at `2.6`. (CI will still run against all of 2.6-3.0)\r\n- Fixes a deprecation warning about `ActiveRecor\n[…]\nassic` autoloader is no longer used/supported in Rails 7)\r\n- Autocorrects some new linter rules pulled from the latest version of the linter gem\r\n- Bumps the version to 0.3.0 and updates the CHANGELOG",
          "is_bot": false,
          "headline": "Add support for Rails 7 (alpha2) (#3)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-10-26T19:52:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d82f789360a8c82404646189441b3739b545859d",
          "body": "I just realized that we hadn't cross-referenced this in the other direction!\r\n\r\nThe linter issues are fixed in #3",
          "is_bot": false,
          "headline": "Plug the blog post in the README (#4)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-10-26T19:25:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "484d68a7a1eea573d52fe12a963002925b9171e9",
          "body": "This fixes an issue I was seeing on a very slim Rails 6.1 app -- essentially, `ActiveSupport.on_load(:active_record)` was not triggering at all during `rake delayed:work`, resulting in a missing constant error. Previous versions of rails seem to load `ActiveRecord::Base` during boot, but 6.1 seems t\n[…]\n to `syck` which dates back to Ruby 1.9, and we should consider removing support for it entirely. (It can still be installed via the `syck` gem, but `psych` has been the default for many, many years.)",
          "is_bot": false,
          "headline": "Minor Rails 6.1 fix, plus autoload Delayed::Job constant (#2)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-09-02T16:45:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "941f02da326ffe13280d5a1cf4c1683a22f886d2",
          "body": "This fixes the gemspec description, which had previously been written in rdoc format (causing it to appear garbled on rubygems.org). It also adds a CHANGELOG and bumps the version to `0.1.1`.",
          "is_bot": false,
          "headline": "Fix rubygems description and add CHANGELOG (#1)",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-08-19T20:29:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "844bbca1e397b500f77eb9d2ecdfef94376cd766",
          "body": null,
          "is_bot": false,
          "headline": "Don't allow alert_age_percent to exceed 100",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-07-22T20:39:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4c61327ec887d229c96e74fb227c2bb5040ca22e",
          "body": "Hello! Here's another stab at the `README` that attempts to incorporate\nall of the feedback I've received, plus describe the zillions of big and\nlittle things that have changed in the 30+ PRs since that initial\ndraft!!!\n\nThis also changes the default queue name to `'default'` (to align with\nActiveJob), and renames the install command from\n`rails g delayed_job:active_record` to `rails g delayed:migration`",
          "is_bot": false,
          "headline": "A README: Our vision for `delayed`",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-25T18:00:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a65965a1a1b158772028fd9ffc2b3e0007ddee24",
          "body": "We were investigating a bug where jobs were not being enqueued with the\ncorrect priority. To rule out `ActiveJob`'s `queue_with_priority`\nconfig, I added this spec. It turns out that it works (the actual bug\nwas with `ActiveJob`'s `retry_on` behavior), but is helpful test\ncoverage to have!",
          "is_bot": false,
          "headline": "Add test for queue_with_priority",
          "author_name": "Harris Effron",
          "author_login": "effron",
          "committed_at": "2021-06-23T17:47:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d8526e62fd31bdb71ff19c6bc704b9f507320d3d",
          "body": "This really only applies in legacy cases where we set DELAY_JOBS=0 -- if\njobs are run inline, they are never technically locked, so there is no\nway of tracking `run_time` via anything db-backed.",
          "is_bot": false,
          "headline": "Fix run_time_alert? when job has no run time",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-22T18:43:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "714ba2259806ffeef7808991a667f6ac573ff9f5",
          "body": "🤦 🤦 🤦\n\nI've been passively trying to figure out why a few of the metrics just\ndidn't seem to line up. Suddenly it struck me while I was making tea\nbefore bed, and so I felt compelled to get a fix up.\n\n...it's because we cached `@as_of = Job.db_time_now` when initializing\nthe monitor instance. I had \n[…]\noves the cleverness and goes back to the old strategy of\nusing an ever-changing `Time.zone.now` (provided by `Job.db_time_now` -\nto be consistent with the rest of the codebase) throughout the queries.",
          "is_bot": false,
          "headline": "Fix metrics that depend on db_time_now",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-22T13:02:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4f92a30a54a105518fc278aa7002b413ce4f5974",
          "body": "I confirmed that this `:eager_load!` initializer is available across all\nof our supported Rails versions. It varies slightly depending on Rails\nversion, so this ensures that the gem's eager-loading behavior will use\nthe same code as whatever Rails version is running, because we call\ndirectly into Rails' eager_load initializer.",
          "is_bot": false,
          "headline": "Run the full eager-loading initializer",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-17T18:02:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ca8a55b9efb3555f63c6cb1ff8bf267862c64df9",
          "body": "…a single value\n\nI baked this check into the matcher, so if it detects events with the\nsame payload but _different_ values, the tests will fail. I then used\nthis to fix the bug and get the tests green.\n\nThis should fix the weird issue where gauge metrics cycle between real\nvalues and zero with no rhyme or reason -- it's because we were emitting\nduplicate metrics with value zero!",
          "is_bot": false,
          "headline": "Ensure that events with matching payloads are only emitted ONCE with …",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-16T21:30:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "190a32b79d5ff62abc3ae93a2f13bbc16f689aa5",
          "body": "Rails always sets `eager_load` to `false` inside of rake tasks, which we\nnever noticed because either:\n\n- we were running the `:classic` autoloader with the single-threaded\n  implementation, so autoloading worked, or\n- we were running the `:zeitwerk` autoloader, which is thread-safe\n\nOn Rails 6.1, w\n[…]\nqueued JobClass (Job ID: SOME-UUID) to Delayed(default)\n```\n\nTo demonstrate the underlying reason:\n\n```\nclass B; end\nA = B\nA.name # => 'B'\n\nA = Class.new(B) # A now inherits from B\nA.name # => 'A'\n```",
          "is_bot": false,
          "headline": "Work around Rails' disabling of eager_load during rake tasks",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-14T17:31:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c7fd37a643b4239113564b8a2c51a4b67d301f39",
          "body": "The way these ENV var handlers were set up was overriding the default\nvalue we set in our initializer (which runs as part of `:environment`).\nThis must be why we relied on a monkey patch (`def self.queues`) rather\nthan setting the queues config directly. Glad to finally have gotten to\nthe bottom of this 😌",
          "is_bot": false,
          "headline": "Fix 'queue(s)' opts when running via rake tasks",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-14T14:16:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6a518c0b1e24467eef4c130caac9104795f3917e",
          "body": "During initialization, Rails uses the `cache_classes` and\n`reload_classes_only_on_change` config to set a \"`check`\" lambda on its\nreloader config:\n\n```ruby\n        if config.cache_classes\n          app.reloader.check = lambda { false }\n        elsif config.reload_classes_only_on_change\n          app\n[…]\n\n\nSo I'm replacing this gem's implementation of `reload_app?` with Rails'\nbuilt-in `.check!` method (which calls the lambda and caches `true`\nvalues until you call `reload!`, which we do immediately).",
          "is_bot": false,
          "headline": "Optimize 'reload!', prevent reloading app every time",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-14T14:15:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6068ad9e7b09b1bc2a17233cdd7f117999391608",
          "body": "I assumed that since tests were passing, I had covered all of my bases.\nBut it turns out we pass opts into `Worker.new` inside of the rake\ntasks, and these were not tested. 🤦 🤕",
          "is_bot": false,
          "headline": "Fix rake tasks, and actually test them",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-08T21:14:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0e02ab44b3c90369ee55f3f77573bcb79180327a",
          "body": "We used the ability to override `attempt_alert_threshold` downstream in\na couple spots. I considered just ditching that functionality with\n`alert_attempts`, etc, but decided that it was easy enough to enable for\nnow.",
          "is_bot": false,
          "headline": "Allow overriding per-job alert thresholds",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-08T18:10:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ebd8c14766057003a02b60da4839e3b055dbd56f",
          "body": null,
          "is_bot": false,
          "headline": "Fix test flake: SimpleJob is not threadsafe",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-08T16:54:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc63b47e373d5f11e6dba06219f04511085bf41c",
          "body": "I caught some issues in runtime relating to ActiveJob default job\npriority behavior.\n\nThis makes two changes:\n\n1. If a priority option is _not_ passed to `set`, this ensures that it\n   doesn't get overridden to `0`. (Essentially, it disobeys class-level\n   priorities that can be set via ActiveJob. -\n[…]\nPreparer` to watch for `payload_object.priority` if it\n   responds to it. (This seems to have been a pre-existing bug in DJ\n   that would prevent the use of ActiveJob class-level priority\n   configs.)",
          "is_bot": false,
          "headline": "Fix default priority behavior with ActiveJobs",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-08T16:52:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1bebd6877bf3cb4f319564b98ad1f7753e97e002",
          "body": "This is my attempt to incorporate the scaling metric\n(`delayed.job.alert_age_percent`) into the monitor process, as well as\nmore general priority-based alert thresholds that can be used to\ndetermine whether or not to fire off alerts when subscribing to the\nActiveSupport notifications.\n\nAlert thresho\n[…]\nic to each priority name. (I\nconsidered adding global defaults too! But I wanted to see how this\nplayed out before making it more complicated.)\n\nCo-authored-by: Harris Effron <harris.effron@gmail.com>",
          "is_bot": false,
          "headline": "Priority based alerts and autoscaling",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-07T20:24:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b278de29fec7b0915dfc26c9668357951ac75246",
          "body": "…tate\n\nAfter chatting offline, we decided to ditch the idea that worker\ninstances could hold their own config state, and instead rely on global\nstate for all of these configs. Things behave mostly the way they did\nbefore now, except that worker instances _don't have setters_ for any of\nthese configs. The only way to change a config is to set it globally on\nthe class.",
          "is_bot": false,
          "headline": "Change default priority to 10, max_claims to 5, and simplify worker s…",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-07T18:02:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f1638c6482303751d0c033090c21d2ddf4f9cb7",
          "body": "We had talked about these! Figured I'd take a stab at them.",
          "is_bot": false,
          "headline": "Add additional instrumentation around job pickup and enqueue",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-07T15:52:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b0db8a46caa39c21f13fb641b3347c65f6d2bcc9",
          "body": "The other new defaults that I had already changed are `max_claims` at 2\n(up from 1) and `destroy_failed_jobs` (set to `false` now).",
          "is_bot": false,
          "headline": "Set max_run_time to 20 minutes by default",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-04T14:57:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "993b757b345a40d112f3c00533054cfc6b5cf02f",
          "body": "…pters\n\nI noticed this when running tests that use the `:test` queue adapter,\nbut it would be true of any other ActiveJob adapters.\n\nActiveJob tries to call `.to_i` on priority (which doesn't work on\nsymbols), which is why I had originally passed it through\n`provider_attributes` instead, but I realized that it's better to just\ntype-cast to a `Delayed::Priority` explicitly so that `to_i` works as\nexpected.",
          "is_bot": false,
          "headline": "Prevent adapter patches from hiding job priority from other queue ada…",
          "author_name": "Nathan Griffith",
          "author_login": "smudge",
          "committed_at": "2021-06-03T19:18:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 26,
      "commits_last_year": 52,
      "latest_release_at": "2026-07-13T15:29:31Z",
      "latest_release_tag": "v3.1.0",
      "releases_from_tags": false,
      "days_since_last_push": 8,
      "active_weeks_last_year": 18,
      "days_since_latest_release": 8,
      "mean_days_between_releases": 31.1
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 37,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "delayed",
          "exists": true,
          "license": "MIT",
          "keywords": [],
          "ecosystem": "rubygems",
          "matches_repo": true,
          "registry_url": "https://rubygems.org/gems/delayed",
          "is_deprecated": false,
          "latest_version": "3.1.0",
          "repository_url": "https://github.com/betterment/delayed",
          "versions_count": 29,
          "total_downloads": 1132655,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": "2021-08-17T14:19:16.612000Z",
          "latest_published_at": "2026-07-13T15:29:09.213000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 8
        }
      ]
    },
    "popularity": {
      "forks": 12,
      "stars": 175,
      "watchers": 44,
      "fork_history": {
        "days": [
          {
            "date": "2021-08-19",
            "count": 1
          },
          {
            "date": "2022-08-17",
            "count": 1
          },
          {
            "date": "2022-10-02",
            "count": 1
          },
          {
            "date": "2023-03-02",
            "count": 1
          },
          {
            "date": "2023-10-15",
            "count": 1
          },
          {
            "date": "2023-12-20",
            "count": 1
          },
          {
            "date": "2024-04-02",
            "count": 1
          },
          {
            "date": "2024-08-13",
            "count": 1
          },
          {
            "date": "2024-10-21",
            "count": 1
          },
          {
            "date": "2026-02-12",
            "count": 1
          },
          {
            "date": "2026-05-21",
            "count": 1
          },
          {
            "date": "2026-05-28",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 12,
        "total_forks": 12
      },
      "star_history": {
        "days": [
          {
            "date": "2021-08-18",
            "count": 1
          },
          {
            "date": "2021-08-19",
            "count": 5
          },
          {
            "date": "2021-08-26",
            "count": 1
          },
          {
            "date": "2021-08-29",
            "count": 1
          },
          {
            "date": "2021-09-09",
            "count": 2
          },
          {
            "date": "2021-09-11",
            "count": 1
          },
          {
            "date": "2021-09-13",
            "count": 1
          },
          {
            "date": "2021-10-05",
            "count": 1
          },
          {
            "date": "2021-10-07",
            "count": 1
          },
          {
            "date": "2021-10-27",
            "count": 1
          },
          {
            "date": "2021-11-08",
            "count": 1
          },
          {
            "date": "2021-11-09",
            "count": 3
          },
          {
            "date": "2021-11-10",
            "count": 1
          },
          {
            "date": "2021-11-23",
            "count": 2
          },
          {
            "date": "2021-11-24",
            "count": 3
          },
          {
            "date": "2021-11-29",
            "count": 1
          },
          {
            "date": "2021-12-10",
            "count": 1
          },
          {
            "date": "2021-12-16",
            "count": 1
          },
          {
            "date": "2021-12-17",
            "count": 2
          },
          {
            "date": "2021-12-20",
            "count": 1
          },
          {
            "date": "2021-12-24",
            "count": 2
          },
          {
            "date": "2021-12-25",
            "count": 1
          },
          {
            "date": "2021-12-26",
            "count": 1
          },
          {
            "date": "2021-12-30",
            "count": 1
          },
          {
            "date": "2022-01-02",
            "count": 1
          },
          {
            "date": "2022-01-06",
            "count": 1
          },
          {
            "date": "2022-01-07",
            "count": 1
          },
          {
            "date": "2022-01-15",
            "count": 1
          },
          {
            "date": "2022-01-22",
            "count": 1
          },
          {
            "date": "2022-01-29",
            "count": 1
          },
          {
            "date": "2022-02-13",
            "count": 1
          },
          {
            "date": "2022-02-14",
            "count": 1
          },
          {
            "date": "2022-02-16",
            "count": 13
          },
          {
            "date": "2022-02-17",
            "count": 1
          },
          {
            "date": "2022-02-18",
            "count": 1
          },
          {
            "date": "2022-02-19",
            "count": 1
          },
          {
            "date": "2022-02-20",
            "count": 1
          },
          {
            "date": "2022-02-21",
            "count": 1
          },
          {
            "date": "2022-02-23",
            "count": 1
          },
          {
            "date": "2022-02-24",
            "count": 1
          },
          {
            "date": "2022-02-28",
            "count": 1
          },
          {
            "date": "2022-04-11",
            "count": 2
          },
          {
            "date": "2022-05-03",
            "count": 1
          },
          {
            "date": "2022-05-09",
            "count": 1
          },
          {
            "date": "2022-05-17",
            "count": 1
          },
          {
            "date": "2022-05-18",
            "count": 1
          },
          {
            "date": "2022-05-24",
            "count": 1
          },
          {
            "date": "2022-05-27",
            "count": 1
          },
          {
            "date": "2022-05-29",
            "count": 1
          },
          {
            "date": "2022-05-30",
            "count": 3
          },
          {
            "date": "2022-06-03",
            "count": 1
          },
          {
            "date": "2022-06-07",
            "count": 1
          },
          {
            "date": "2022-06-20",
            "count": 3
          },
          {
            "date": "2022-06-27",
            "count": 1
          },
          {
            "date": "2022-07-07",
            "count": 1
          },
          {
            "date": "2022-07-28",
            "count": 1
          },
          {
            "date": "2022-08-15",
            "count": 1
          },
          {
            "date": "2022-08-21",
            "count": 1
          },
          {
            "date": "2022-08-24",
            "count": 1
          },
          {
            "date": "2022-08-26",
            "count": 1
          },
          {
            "date": "2022-09-02",
            "count": 1
          },
          {
            "date": "2022-09-04",
            "count": 1
          },
          {
            "date": "2022-09-15",
            "count": 1
          },
          {
            "date": "2022-09-16",
            "count": 1
          },
          {
            "date": "2022-10-15",
            "count": 3
          },
          {
            "date": "2022-10-21",
            "count": 1
          },
          {
            "date": "2022-11-04",
            "count": 1
          },
          {
            "date": "2022-11-07",
            "count": 1
          },
          {
            "date": "2022-11-09",
            "count": 1
          },
          {
            "date": "2022-11-28",
            "count": 1
          },
          {
            "date": "2022-12-07",
            "count": 1
          },
          {
            "date": "2023-01-26",
            "count": 1
          },
          {
            "date": "2023-02-03",
            "count": 1
          },
          {
            "date": "2023-02-06",
            "count": 1
          },
          {
            "date": "2023-02-07",
            "count": 1
          },
          {
            "date": "2023-03-08",
            "count": 1
          },
          {
            "date": "2023-03-09",
            "count": 1
          },
          {
            "date": "2023-04-05",
            "count": 1
          },
          {
            "date": "2023-04-10",
            "count": 1
          },
          {
            "date": "2023-05-02",
            "count": 1
          },
          {
            "date": "2023-05-03",
            "count": 1
          },
          {
            "date": "2023-05-05",
            "count": 1
          },
          {
            "date": "2023-05-09",
            "count": 1
          },
          {
            "date": "2023-06-09",
            "count": 1
          },
          {
            "date": "2023-06-11",
            "count": 1
          },
          {
            "date": "2023-06-12",
            "count": 1
          },
          {
            "date": "2023-06-20",
            "count": 1
          },
          {
            "date": "2023-07-22",
            "count": 1
          },
          {
            "date": "2023-08-04",
            "count": 1
          },
          {
            "date": "2023-08-08",
            "count": 1
          },
          {
            "date": "2023-08-19",
            "count": 1
          },
          {
            "date": "2023-09-17",
            "count": 2
          },
          {
            "date": "2023-10-02",
            "count": 1
          },
          {
            "date": "2023-10-12",
            "count": 1
          },
          {
            "date": "2023-10-13",
            "count": 1
          },
          {
            "date": "2023-10-14",
            "count": 1
          },
          {
            "date": "2023-10-18",
            "count": 1
          },
          {
            "date": "2023-11-18",
            "count": 1
          },
          {
            "date": "2023-11-26",
            "count": 1
          },
          {
            "date": "2023-12-03",
            "count": 1
          },
          {
            "date": "2023-12-18",
            "count": 1
          },
          {
            "date": "2023-12-22",
            "count": 1
          },
          {
            "date": "2024-01-10",
            "count": 1
          },
          {
            "date": "2024-01-13",
            "count": 1
          },
          {
            "date": "2024-01-18",
            "count": 1
          },
          {
            "date": "2024-02-09",
            "count": 1
          },
          {
            "date": "2024-02-18",
            "count": 1
          },
          {
            "date": "2024-03-25",
            "count": 1
          },
          {
            "date": "2024-04-15",
            "count": 1
          },
          {
            "date": "2024-04-18",
            "count": 1
          },
          {
            "date": "2024-05-08",
            "count": 1
          },
          {
            "date": "2024-05-31",
            "count": 1
          },
          {
            "date": "2024-06-13",
            "count": 1
          },
          {
            "date": "2024-07-17",
            "count": 1
          },
          {
            "date": "2024-07-18",
            "count": 1
          },
          {
            "date": "2024-08-21",
            "count": 1
          },
          {
            "date": "2024-10-18",
            "count": 1
          },
          {
            "date": "2024-10-27",
            "count": 1
          },
          {
            "date": "2024-11-20",
            "count": 1
          },
          {
            "date": "2025-05-15",
            "count": 3
          },
          {
            "date": "2025-05-20",
            "count": 1
          },
          {
            "date": "2025-05-25",
            "count": 1
          },
          {
            "date": "2025-09-16",
            "count": 1
          },
          {
            "date": "2025-09-18",
            "count": 1
          },
          {
            "date": "2025-09-19",
            "count": 2
          },
          {
            "date": "2025-09-25",
            "count": 1
          },
          {
            "date": "2025-09-27",
            "count": 1
          },
          {
            "date": "2025-10-07",
            "count": 1
          },
          {
            "date": "2025-10-28",
            "count": 1
          },
          {
            "date": "2025-12-09",
            "count": 1
          },
          {
            "date": "2026-01-08",
            "count": 1
          },
          {
            "date": "2026-01-09",
            "count": 1
          },
          {
            "date": "2026-02-19",
            "count": 1
          },
          {
            "date": "2026-03-04",
            "count": 1
          },
          {
            "date": "2026-03-11",
            "count": 1
          },
          {
            "date": "2026-03-18",
            "count": 1
          },
          {
            "date": "2026-05-26",
            "count": 1
          },
          {
            "date": "2026-07-01",
            "count": 1
          },
          {
            "date": "2026-07-09",
            "count": 1
          },
          {
            "date": "2026-07-14",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 175,
        "total_stars": 175
      },
      "open_issues_and_prs": 9
    },
    "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": [],
      "toolchain_manifests": [],
      "largest_source_bytes": 48940,
      "source_files_sampled": 58,
      "oversized_source_files": 0,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "Gemfile"
      ],
      "advisories": {
        "error": null,
        "scope": "repository_graph",
        "source": "osv",
        "findings": [
          {
            "name": "rack-session",
            "direct": false,
            "version": "2.1.0",
            "severity": "critical",
            "ecosystem": "rubygems",
            "cvss_score": 9.1,
            "advisory_ids": [
              "GHSA-33qg-7wpp-89cq",
              "GHSA-9j94-67jr-4cqj"
            ],
            "fixed_version": "2.1.2",
            "advisory_count": 2,
            "oldest_advisory_days": 439
          },
          {
            "name": "activesupport",
            "direct": false,
            "version": "8.0.2",
            "severity": "high",
            "ecosystem": "rubygems",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-2j26-frm8-cmj9",
              "GHSA-89vf-4333-qx8v",
              "GHSA-cg4j-q9v8-6v38"
            ],
            "fixed_version": "8.1.2.1",
            "advisory_count": 3,
            "oldest_advisory_days": 120
          },
          {
            "name": "concurrent-ruby",
            "direct": false,
            "version": "1.3.5",
            "severity": "high",
            "ecosystem": "rubygems",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-6wx8-w4f5-wwcr",
              "GHSA-h8w8-99g7-qmvj",
              "GHSA-wv3x-4vxv-whpp"
            ],
            "fixed_version": "1.3.7",
            "advisory_count": 3,
            "oldest_advisory_days": 32
          },
          {
            "name": "nokogiri",
            "direct": false,
            "version": "1.18.6",
            "severity": "high",
            "ecosystem": "rubygems",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-353f-x4gh-cqq8",
              "GHSA-5prr-v3j2-97mh",
              "GHSA-5v8h-3h3q-446p",
              "GHSA-5w6v-399v-w3cc",
              "GHSA-8678-w3jw-xfc2",
              "GHSA-9cv2-cfxc-v4v2",
              "GHSA-c4rq-3m3g-8wgx",
              "GHSA-p67v-3w7g-wjg7",
              "GHSA-phwj-rprq-35pp",
              "GHSA-v2fc-qm4h-8hqv"
            ],
            "fixed_version": "1.19.4",
            "advisory_count": 13,
            "oldest_advisory_days": 455
          },
          {
            "name": "rack",
            "direct": false,
            "version": "3.1.12",
            "severity": "high",
            "ecosystem": "rubygems",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-47m2-26rw-j2jw",
              "GHSA-6xw4-3v39-52mm",
              "GHSA-7mqq-6cf9-v2qp",
              "GHSA-8vqr-qjwx-82mw",
              "GHSA-g2pf-xv49-m2h5",
              "GHSA-gjh7-p2fx-99vx",
              "GHSA-h2jq-g4cq-5ppq",
              "GHSA-mxw3-3hh2-x2mh",
              "GHSA-p543-xpfm-54cp",
              "GHSA-q2ww-5357-x388"
            ],
            "fixed_version": "3.2.6",
            "advisory_count": 21,
            "oldest_advisory_days": 439
          },
          {
            "name": "activerecord",
            "direct": true,
            "version": "8.0.2",
            "severity": "moderate",
            "ecosystem": "rubygems",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-76r7-hhxj-r776"
            ],
            "fixed_version": "8.0.2.1",
            "advisory_count": 1,
            "oldest_advisory_days": 341
          },
          {
            "name": "net-imap",
            "direct": false,
            "version": "0.5.6",
            "severity": "moderate",
            "ecosystem": "rubygems",
            "cvss_score": 5.3,
            "advisory_ids": [
              "GHSA-46q3-7gv7-qmgg",
              "GHSA-75xq-5h9v-w6px",
              "GHSA-87pf-fpwv-p7m7",
              "GHSA-8p34-64r3-mwg8",
              "GHSA-c4fp-cxrr-mj66",
              "GHSA-hm49-wcqc-g2xg",
              "GHSA-j3g3-5qv5-52mj",
              "GHSA-q2mw-fvj9-vvcw",
              "GHSA-vcgp-9326-pqcp"
            ],
            "fixed_version": "0.6.4.1",
            "advisory_count": 9,
            "oldest_advisory_days": 449
          },
          {
            "name": "actionview",
            "direct": false,
            "version": "8.0.2",
            "severity": "low",
            "ecosystem": "rubygems",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-v55j-83pf-r9cq"
            ],
            "fixed_version": "8.1.2.1",
            "advisory_count": 1,
            "oldest_advisory_days": 120
          }
        ],
        "collected": true,
        "truncated": false,
        "by_severity": {
          "low": 1,
          "high": 4,
          "critical": 1,
          "moderate": 2
        },
        "advisory_count": 53,
        "affected_count": 8,
        "assessed_count": 78,
        "assessed_package": null,
        "unassessed_count": 2,
        "direct_affected_count": 1
      },
      "ecosystems": [
        "rubygems"
      ],
      "dependencies": [
        {
          "name": "actionmailer",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "activejob",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "activerecord",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "appraisal",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "betterlint",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "mysql2",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "pg",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "rake",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "rspec",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "timecop",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "zeitwerk",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "sqlite3",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": ">= 1.4"
        },
        {
          "name": "snapshot_testing",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        }
      ],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "actionmailer",
            "direct": true,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "activejob",
            "direct": true,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "activerecord",
            "direct": true,
            "version": null,
            "ecosystem": "rubygems"
          },
          {
            "name": "activerecord",
            "direct": true,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "appraisal",
            "direct": true,
            "version": "2.5.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "betterlint",
            "direct": true,
            "version": "1.19.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "mysql2",
            "direct": true,
            "version": "0.5.6",
            "ecosystem": "rubygems"
          },
          {
            "name": "pg",
            "direct": true,
            "version": "1.5.9",
            "ecosystem": "rubygems"
          },
          {
            "name": "rake",
            "direct": true,
            "version": "13.2.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rspec",
            "direct": true,
            "version": "3.13.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "snapshot_testing",
            "direct": true,
            "version": "0.3.4",
            "ecosystem": "rubygems"
          },
          {
            "name": "sqlite3",
            "direct": true,
            "version": "2.6.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "timecop",
            "direct": true,
            "version": "0.9.10",
            "ecosystem": "rubygems"
          },
          {
            "name": "zeitwerk",
            "direct": true,
            "version": "2.7.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "actionpack",
            "direct": false,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "actionview",
            "direct": false,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "activemodel",
            "direct": false,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "activesupport",
            "direct": false,
            "version": "8.0.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "ast",
            "direct": false,
            "version": "2.4.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "base64",
            "direct": false,
            "version": "0.2.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "benchmark",
            "direct": false,
            "version": "0.4.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "bigdecimal",
            "direct": false,
            "version": "3.1.9",
            "ecosystem": "rubygems"
          },
          {
            "name": "builder",
            "direct": false,
            "version": "3.3.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "concurrent-ruby",
            "direct": false,
            "version": null,
            "ecosystem": "rubygems"
          },
          {
            "name": "concurrent-ruby",
            "direct": false,
            "version": "1.3.5",
            "ecosystem": "rubygems"
          },
          {
            "name": "connection_pool",
            "direct": false,
            "version": "2.5.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "crass",
            "direct": false,
            "version": "1.0.6",
            "ecosystem": "rubygems"
          },
          {
            "name": "date",
            "direct": false,
            "version": "3.4.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "diff-lcs",
            "direct": false,
            "version": "1.6.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "drb",
            "direct": false,
            "version": "2.2.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "erubi",
            "direct": false,
            "version": "1.13.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "globalid",
            "direct": false,
            "version": "1.2.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "i18n",
            "direct": false,
            "version": "1.14.7",
            "ecosystem": "rubygems"
          },
          {
            "name": "json",
            "direct": false,
            "version": "2.10.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "language_server-protocol",
            "direct": false,
            "version": "3.17.0.4",
            "ecosystem": "rubygems"
          },
          {
            "name": "lint_roller",
            "direct": false,
            "version": "1.1.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "logger",
            "direct": false,
            "version": "1.7.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "loofah",
            "direct": false,
            "version": "2.24.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "mail",
            "direct": false,
            "version": "2.8.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "mini_mime",
            "direct": false,
            "version": "1.1.5",
            "ecosystem": "rubygems"
          },
          {
            "name": "minitest",
            "direct": false,
            "version": "5.25.5",
            "ecosystem": "rubygems"
          },
          {
            "name": "net-imap",
            "direct": false,
            "version": "0.5.6",
            "ecosystem": "rubygems"
          },
          {
            "name": "net-pop",
            "direct": false,
            "version": "0.1.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "net-protocol",
            "direct": false,
            "version": "0.2.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "net-smtp",
            "direct": false,
            "version": "0.5.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "nokogiri",
            "direct": false,
            "version": "1.18.6",
            "ecosystem": "rubygems"
          },
          {
            "name": "parallel",
            "direct": false,
            "version": "1.26.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "parser",
            "direct": false,
            "version": "3.3.7.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "prism",
            "direct": false,
            "version": "1.4.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "racc",
            "direct": false,
            "version": "1.8.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rack",
            "direct": false,
            "version": "3.1.12",
            "ecosystem": "rubygems"
          },
          {
            "name": "rack-session",
            "direct": false,
            "version": "2.1.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rack-test",
            "direct": false,
            "version": "2.2.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rails-dom-testing",
            "direct": false,
            "version": "2.2.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rails-html-sanitizer",
            "direct": false,
            "version": "1.6.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "rainbow",
            "direct": false,
            "version": "3.1.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "regexp_parser",
            "direct": false,
            "version": "2.10.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rspec-core",
            "direct": false,
            "version": "3.13.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "rspec-expectations",
            "direct": false,
            "version": "3.13.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "rspec-mocks",
            "direct": false,
            "version": "3.13.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "rspec-support",
            "direct": false,
            "version": "3.13.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop",
            "direct": false,
            "version": "1.75.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-ast",
            "direct": false,
            "version": "1.43.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-capybara",
            "direct": false,
            "version": "2.22.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-factory_bot",
            "direct": false,
            "version": "2.27.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-graphql",
            "direct": false,
            "version": "1.5.5",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-performance",
            "direct": false,
            "version": "1.24.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-rails",
            "direct": false,
            "version": "2.30.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-rake",
            "direct": false,
            "version": "0.7.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-rspec",
            "direct": false,
            "version": "2.31.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "rubocop-rspec_rails",
            "direct": false,
            "version": "2.29.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "ruby-progressbar",
            "direct": false,
            "version": "1.13.0",
            "ecosystem": "rubygems"
          },
          {
            "name": "securerandom",
            "direct": false,
            "version": "0.4.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "thor",
            "direct": false,
            "version": "1.3.2",
            "ecosystem": "rubygems"
          },
          {
            "name": "timeout",
            "direct": false,
            "version": "0.4.3",
            "ecosystem": "rubygems"
          },
          {
            "name": "tzinfo",
            "direct": false,
            "version": "2.0.6",
            "ecosystem": "rubygems"
          },
          {
            "name": "unicode-display_width",
            "direct": false,
            "version": "3.1.4",
            "ecosystem": "rubygems"
          },
          {
            "name": "unicode-emoji",
            "direct": false,
            "version": "4.0.4",
            "ecosystem": "rubygems"
          },
          {
            "name": "uri",
            "direct": false,
            "version": "1.1.1",
            "ecosystem": "rubygems"
          },
          {
            "name": "useragent",
            "direct": false,
            "version": "0.16.11",
            "ecosystem": "rubygems"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 80,
        "direct_count": 14,
        "indirect_count": 66
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 3,
        "merged_prs": 81,
        "open_issues": 6,
        "closed_ratio": 0.76,
        "closed_issues": 19,
        "closed_unmerged_prs": 7
      },
      "bus_factor": 3,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "albus522",
          "commits": 335,
          "avatar_url": "https://avatars.githubusercontent.com/u/7812?v=4"
        },
        {
          "type": "User",
          "login": "bkeepers",
          "commits": 228,
          "avatar_url": "https://avatars.githubusercontent.com/u/173?v=4"
        },
        {
          "type": "User",
          "login": "sferik",
          "commits": 131,
          "avatar_url": "https://avatars.githubusercontent.com/u/10308?v=4"
        },
        {
          "type": "User",
          "login": "bryckbost",
          "commits": 96,
          "avatar_url": "https://avatars.githubusercontent.com/u/692?v=4"
        },
        {
          "type": "User",
          "login": "smudge",
          "commits": 96,
          "avatar_url": "https://avatars.githubusercontent.com/u/83998?v=4"
        },
        {
          "type": "User",
          "login": "betamatt",
          "commits": 65,
          "avatar_url": "https://avatars.githubusercontent.com/u/23032?v=4"
        },
        {
          "type": "User",
          "login": "laserlemon",
          "commits": 34,
          "avatar_url": "https://avatars.githubusercontent.com/u/34264?v=4"
        },
        {
          "type": "User",
          "login": "lpetre",
          "commits": 12,
          "avatar_url": "https://avatars.githubusercontent.com/u/193260?v=4"
        },
        {
          "type": "User",
          "login": "gaffneyc",
          "commits": 10,
          "avatar_url": "https://avatars.githubusercontent.com/u/7095?v=4"
        },
        {
          "type": "User",
          "login": "bethesque",
          "commits": 8,
          "avatar_url": "https://avatars.githubusercontent.com/u/446228?v=4"
        }
      ],
      "contributors_sampled": 100,
      "top_contributor_share": 0.275
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "linter.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [
        ".rubocop.yml"
      ],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "Gemfile.lock"
      ],
      "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": 8,
            "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": "30 out of 30 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": 10,
            "reason": "all changesets reviewed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 10,
            "reason": "project has 34 contributing companies or organizations",
            "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": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 7,
            "reason": "9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": null,
            "reason": "packaging workflow not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 10,
            "reason": "all dependencies are pinned",
            "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": 0,
            "reason": "53 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "c726753839bef4bec1b4d5dbe9945cb43d487edc",
        "ran_at": "2026-07-21T21:02:01Z",
        "aggregate_score": 5.3,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/Betterment/delayed",
    "host": "github.com",
    "name": "delayed",
    "owner": "Betterment"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "good",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 71,
      "inputs": {
        "security": 59,
        "vitality": 78,
        "community": 59,
        "governance": 82,
        "engineering": 70
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 78,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 64,
            "inputs": {
              "commits_last_year": 52,
              "human_commit_share": 1,
              "days_since_last_push": 8,
              "active_weeks_last_year": 18
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 8 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "18/52 weeks with commits",
                "points": 12.5,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 18
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "52 commits in the last year",
                "points": 15.5,
                "status": "partial",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 52
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7",
                "points": 7,
                "status": "partial",
                "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": 26,
              "latest_release_tag": "v3.1.0",
              "releases_from_tags": false,
              "days_since_latest_release": 8,
              "mean_days_between_releases": 31.1
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "26 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 26
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 8 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~31.1 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 31.1
                    }
                  }
                ],
                "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": "maintained",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": null,
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": 8,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "last human commit 8 days ago",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_maintained",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "moderate",
        "name": "Community & Adoption",
        "value": 59,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "moderate",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 54,
            "inputs": {
              "forks": 12,
              "stars": 175,
              "watchers": 44,
              "growth_state": "organic",
              "growth_factor_pct": 100
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "175 stars",
                "points": 36.3,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 175
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "12 forks",
                "points": 8.7,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 12
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "44 watchers",
                "points": 9.1,
                "status": "partial",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 44
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "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": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "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": "good",
            "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": 79,
            "inputs": {
              "packages": [
                "delayed"
              ],
              "dependents": null,
              "ecosystems": "rubygems",
              "total_downloads": 1132655,
              "monthly_downloads": null
            },
            "components": [
              {
                "key": "total_downloads",
                "name": "Total downloads",
                "detail": "1,132,655 downloads all-time across rubygems",
                "points": 62.9,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_total",
                    "params": {
                      "count": 1132655,
                      "ecosystems": "rubygems"
                    }
                  }
                ],
                "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": "good",
        "name": "Sustainability & Governance",
        "value": 82,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "good",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 76,
            "inputs": {
              "bus_factor": 3,
              "contributors_sampled": 100,
              "top_contributor_share": 0.275
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "3 contributor(s) cover half of all commits",
                "points": 36,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 28% of commits",
                "points": 16.3,
                "status": "partial",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 28
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "100 contributors",
                "points": 13.5,
                "status": "met",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 100
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 34 contributing companies or organizations",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "excellent",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 86,
            "inputs": {
              "merged_prs": 81,
              "open_issues": 6,
              "closed_issues": 19,
              "issue_closed_ratio": 0.76,
              "closed_unmerged_prs": 7
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "76% of issues closed",
                "points": 35.5,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 76
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "81/88 decided PRs merged",
                "points": 35.2,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 81,
                      "decided": 88
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "all changesets reviewed",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "good",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 70,
            "inputs": {
              "followers": 105,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "Betterment",
              "public_repos": 91,
              "account_age_days": 5507
            },
            "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": "105 followers of Betterment",
                "points": 14.6,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 105,
                      "login": "Betterment"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "91 public repos, account ~15 yr old",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 91
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 15
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "delayed"
              ],
              "ecosystems": "rubygems",
              "any_deprecated": false,
              "min_days_since_publish": 8
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on rubygems",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "rubygems"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 8 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "29 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 29
                    }
                  }
                ],
                "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": "good",
        "name": "Engineering Quality",
        "value": 70,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "good",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 84,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "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": ".rubocop.yml",
                "points": 16,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": ".rubocop.yml"
                    }
                  }
                ],
                "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": "30 out of 30 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "moderate",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": false,
              "has_description": true
            },
            "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": 10,
                "status": "met",
                "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": 59,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Packaging, Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "packaging",
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 53,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "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": 6,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "30 out of 30 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": "all changesets reviewed",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 34 contributing companies or organizations",
                "points": 2.5,
                "status": "met",
                "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 detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7",
                "points": 5.2,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow not detected",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "all dependencies are pinned",
                "points": 5,
                "status": "met",
                "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": "53 existing vulnerabilities detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories. Remaining weights renormalized. Matched 78 resolved dependencies against OSV; 2 could not be assessed (no resolved version, an unsupported ecosystem, or beyond the reported package list). This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "indirect_dependencies_free_of_known_advisories"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_repository",
                "params": {
                  "assessed": 78
                }
              },
              {
                "code": "advisories_unassessed",
                "params": {
                  "count": 2
                }
              },
              {
                "code": "advisories_repo_graph_caveat",
                "params": {}
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 85,
            "inputs": {
              "source": "osv",
              "advisories": 53,
              "affected_packages": 8,
              "assessed_packages": 78,
              "unassessed_packages": 2,
              "affected_by_severity": "critical 1, high 4, moderate 2, low 1",
              "direct_affected_packages": 1
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "1 affected: activerecord 8.0.2 (moderate)",
                "points": 26.6,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_affected",
                    "params": {
                      "count": 1,
                      "packages": "activerecord 8.0.2 (moderate)"
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "transitive set not separable from development and test dependencies in this scope",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_scope_not_separable",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "1 advisory-carrying package(s) unaddressed past 90 days; oldest published 341 days ago",
                "points": 37.2,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_stale",
                    "params": {
                      "days": 90,
                      "count": 1,
                      "oldest": 341
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 78,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "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": 19
            },
            "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": "moderate",
        "name": "AI Readiness",
        "value": 51,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.97,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "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": "97 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 97,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 57,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "Gemfile.lock"
              ],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [],
              "agent_commit_share": 0.02,
              "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": ".rubocop.yml",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": ".rubocop.yml"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "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": "2 of the last 100 commits agent-authored or agent-credited",
                "points": 4,
                "status": "partial",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 2,
                      "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": "all dependencies are pinned",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "moderate",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 55,
            "inputs": {
              "primary_language": "Ruby",
              "largest_source_bytes": 48940,
              "source_files_sampled": 58,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Ruby without a type-check config",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_typecheck_config_language",
                    "params": {
                      "language": "Ruby"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/58 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 58,
                      "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": [],
  "report_type": "repository",
  "generated_at": "2026-07-21T21:02:32.815288Z",
  "schema_version": "0.23.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/b/Betterment/delayed.svg",
  "full_name": "Betterment/delayed",
  "license_state": "standard",
  "license_spdx": "MIT"
}

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v1.13.0、模式 v0.23.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计RubyGems.