Thank you for helping. Read docs/DESIGN.md first: it is the shared contract of all seven Didit server SDKs, and when the code and the design disagree, the code is wrong.
You need Ruby 3.1 or newer and Bundler. The type check (Steep) needs Ruby 3.3 or newer; on older Rubies the Gemfile leaves it out.
bundle install| Path | What | Edited by hand |
|---|---|---|
openapi/verification.json, openapi/account.json, openapi/webhook-vectors.json |
Upstream copies | Never (spec sync only) |
openapi/overlay.json |
SDK naming (x-didit-sdk) for operations that do not carry it upstream yet |
Yes |
openapi/deprecated-operations.json |
Removed or renamed operations, kept as deprecated methods for one minor version | Spec sync only |
codegen/ |
The generator and its tests (codegen/spec) |
Yes |
lib/didit/resources/, lib/didit/resources.rb, lib/didit/types/, lib/didit/types.rb, sig/didit/resources/, sig/didit/resources.rbs, sig/didit/types/, spec/generated/ |
Generated layer | Never |
lib/didit.rb, lib/didit/*.rb, lib/didit/internal/, lib/didit/webhooks*, lib/didit/authentication* |
Hand-written core: client, transport, retries, errors, pagination, files, webhooks, Authentication | Yes |
sig/ (outside the generated paths) |
RBS signatures of the core, one file per lib/ file |
Yes |
Generated files start with Code generated by codegen from openapi/*.json. DO NOT EDIT. To change them, change the generator in codegen/ or openapi/overlay.json, then regenerate. CI fails when the committed generated code is stale.
bundle exec rake generate # regenerate the generated layer from openapi/
bundle exec rake generate:check # fail when the committed generated code is stale
bundle exec rake spec # RSpec over spec/ (live tests are skipped)
bundle exec rake codegen:spec # generator tests in codegen/spec
bundle exec rake rubocop # RuboCop
bundle exec steep check # RBS signatures checked with Steep (Ruby 3.3+)
bundle exec rake build # build the gem into pkg/ (never publishes)
COVERAGE=1 bundle exec rspec # fails under 90 % line coverage of the hand-written core
ruby codegen/spec_sync.rb # download the upstream spec, regenerate, summarise the changesbundle exec rake runs generate:check, spec, codegen:spec and rubocop.
The API surface comes from the OpenAPI documents published at docs.didit.me (mintlify-docs). To add or change an endpoint, change the backend and its OpenAPI document there; the spec sync then brings it here and to the other six SDKs.
openapi/overlay.jsonholds the naming of operations that do not carryx-didit-sdkupstream. Keys are"<METHOD> <path>"forverification.jsonand"account <METHOD> <path>"foraccount.json; each value hasresource(dot path of snake_case names),method,auth,serverand, for application-scoped paths,scope(DESIGN section 4). When upstream carriesx-didit-sdk, the upstream value wins and the overlay entry is deleted.- A new server operation without naming makes the generator fail with a message that names it. The fix is an overlay entry, or
x-didit-sdkupstream. - A removed or renamed operation is never deleted silently: the spec sync records it in
openapi/deprecated-operations.jsonand the generator keeps it as a deprecated method for one minor version (DESIGN section 3.1). - After any change in
openapi/orcodegen/, runbundle exec rake generateand commit the result with the change.
.github/workflows/spec-sync.yml runs daily. When an upstream document changed, it regenerates the code, runs the checks and opens a pull request titled chore(spec): sync API spec YYYY-MM-DD whose body lists added, removed and changed operations and schemas and calls out anything breaking. The gems, the generator and the checks run in a job with a read-only token; a second job, which installs nothing, pushes the commit and opens or updates the pull request.
You may push commits to the branch of that pull request (for example an overlay entry when the generator asks for one). Later syncs do not overwrite a branch that holds commits from people; merge or close the pull request and the next sync picks up anything that changed since. If a run pushed the branch but could not open the pull request (for example before the Actions setting below was ticked), the next run opens it.
CI (.github/workflows/ci.yml) runs the tests on Ruby 3.1, 3.2, 3.3, 3.4 and 4.0 on Linux and on Ruby 3.4 on macOS and Windows, the Authentication tests with jwt 2.7.0 (the gemspec's floor) and 2.10.2 (JWT_VERSION), RuboCop, Steep, the generated code check, the coverage gate, a build and clean install of the gem, a gitleaks scan of the full history (.gitleaks.toml) and a check that CLAUDE.md and AGENTS.md are identical. Run the same commands before you open a pull request.
Unit tests never touch the network (WebMock blocks it). Fixtures are synthetic: test webhook secrets start with whsec_test_, and keys and tokens are generated at test time.
Live smoke tests are tagged live: true and run only when DIDIT_SANDBOX_API_KEY holds the API key of a Didit sandbox application; DIDIT_SANDBOX_WORKFLOW_ID is optional. They never run in CI.
DIDIT_SANDBOX_API_KEY=... bundle exec rspec --tag livePull request titles follow Conventional Commits with the type that matches the semver impact: fix: is a patch, feat: a minor version, and feat!: or a BREAKING CHANGE: footer a major version (a minor version while the SDK is 0.x). chore(spec): sync pull requests appear in the changelog and release at least a patch version; other chore, ci, docs, test, refactor, style and build commits release nothing on their own. Dependabot pull requests are chore(deps) and ci; retitle one that changes a runtime dependency in didit-sdk.gemspec to fix(deps): ... so that it ships.
Pull requests are squash-merged, so the title becomes the commit that release-please reads. Public API changes need tests and a README update.
release-please (.github/workflows/release.yml) keeps a release pull request open with the next version (lib/didit/version.rb, .release-please-manifest.json) and the generated CHANGELOG.md. Never edit the changelog by hand. Merging the release pull request makes the next run create the vX.Y.Z tag and the GitHub release. The build job of the same workflow then checks out the tag, checks that it equals Didit::VERSION, runs the full test suite and builds the gem. The publish job waits for a reviewer of the rubygems environment; once approved, it checks that the gem is the one the build job tested, attests its build provenance, attaches it to the GitHub release and pushes it to RubyGems.org with trusted publishing. Only the publish job can obtain the OIDC token, and it installs and runs nothing from the repository or its bundle. If a step fails for a transient reason, re-run the failed jobs (RubyGems.org refuses a second push of the same version); if the tagged code itself fails, fix main and release the next patch version.
1.0.0 was released on 2026-10-29. Merging the release pull request is the release approval (Alex, 2026-10-29); nobody tags or publishes by hand. The workflow runs while the repository variable RELEASES_ENABLED is true.
To force a specific version, merge a commit on main whose message ends with the footer Release-As: X.Y.Z first.
- RubyGems.org: use the Didit account (owner
didit) with MFA enabled, and add the other Didit maintainers as owners ofdidit-sdkonce it exists, each with MFA. - Trusted publisher:
didit-sdkdoes not exist on RubyGems.org yet, so add a pending trusted publisher from the account settings (gem namedidit-sdk, GitHub Actions, repositorydidit-protocol/sdk-ruby, workflow filerelease.yml, environmentrubygems). The first push from the workflow creates the gem; after that the publisher is listed on the gem's trusted publishers page. No API key is stored in GitHub. - GitHub environment
rubygems(Settings > Environments): the trusted publisher's environment, with no required reviewers (merging the release pull request is the approval); deployment branches may be limited tomain(the publish job runs on pushes tomain). Create it before settingRELEASES_ENABLED: a job that names a missing environment creates it without any protection rule. - Tag ruleset for
v*(Settings > Rules > Rulesets): restrict creation, update and deletion, with only the identity that creates the release tags on the bypass list (the role, team or GitHub App behindRELEASE_PLEASE_TOKEN). If that identity cannot be added to the bypass list (for example the defaultGITHUB_TOKEN), restrict update and deletion only. - Settings > Actions > General: "Allow GitHub Actions to create and approve pull requests", so release-please and the spec sync can open their pull requests.
- Optional repository secret
RELEASE_PLEASE_TOKEN: a fine-grained token with contents, issues and pull requests read and write on this repository only (for a GitHub App, mint its token in the workflow withactions/create-github-app-tokenand pass that instead). Without it the release pull request gets no CI run, because pull requests opened with the defaultGITHUB_TOKENstart no workflows. - Settings > Collaborators and teams: give
@didit-protocol/backend, the code owners in.github/CODEOWNERS, write access; GitHub ignores code owners without it. - The repository is public and the repository variable
RELEASES_ENABLEDistrue(both since 2026-10-29). - Check a release with
gh attestation verify didit-sdk-X.Y.Z.gem --repo didit-protocol/sdk-ruby.
Didit has seven server SDKs: sdk-python, sdk-node, sdk-go, sdk-java, sdk-php, sdk-ruby and sdk-dotnet. This is a hard rule (see CLAUDE.md):
- Any public API change in
service-didit-verificationorservice-didit-authmust reach the OpenAPI documents in mintlify-docs, and from there every server SDK through the spec sync pull request. - A change to hand-written core behaviour that DESIGN.md describes (client options, errors, retries, pagination, webhooks, Authentication) is made in all seven repositories, one pull request each, and DESIGN.md is updated in all seven in the same words.
- Feature parity: a capability shipped in one server SDK ships in the other six.
Every pull request states its parity: the same change in the other six repositories, or why it does not apply.
Report vulnerabilities privately, never in an issue: see SECURITY.md. Never commit secrets, API keys or real customer data.