familia 2.10.1 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +15 -0
  3. data/.github/workflows/ci.yml +2 -5
  4. data/.github/workflows/claude-code-review.yml +33 -9
  5. data/.github/workflows/claude.yml +9 -2
  6. data/.github/workflows/code-smells.yml +2 -2
  7. data/.github/workflows/codeql.yml +101 -0
  8. data/.github/workflows/release-gem.yml +161 -0
  9. data/.github/workflows/ruby-lint.yml +66 -0
  10. data/.github/workflows/yardoc.yml +110 -0
  11. data/.rubocop.yml +3 -1
  12. data/.yardopts +1 -1
  13. data/CHANGELOG.rst +207 -0
  14. data/Gemfile +9 -3
  15. data/Gemfile.lock +15 -22
  16. data/README.md +26 -0
  17. data/Rakefile +29 -0
  18. data/docs/guides/feature-encrypted-fields.md +45 -12
  19. data/docs/investigation/memory-audit.md +465 -0
  20. data/docs/overview.md +1 -1
  21. data/docs/reference/api-technical.md +2 -1
  22. data/examples/encryption_upgrade_proof/.gitignore +3 -0
  23. data/examples/encryption_upgrade_proof/README.md +84 -0
  24. data/examples/encryption_upgrade_proof/common.rb +83 -0
  25. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-no-libsodium +4 -0
  26. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-with-libsodium +5 -0
  27. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.production-today +4 -0
  28. data/examples/encryption_upgrade_proof/model.rb +32 -0
  29. data/examples/encryption_upgrade_proof/phase0_production_today.rb +100 -0
  30. data/examples/encryption_upgrade_proof/phase1_gem_upgrade_no_libsodium.rb +99 -0
  31. data/examples/encryption_upgrade_proof/phase2_libsodium_enabled.rb +297 -0
  32. data/examples/encryption_upgrade_proof/phase3_rollback_hazard.rb +56 -0
  33. data/examples/encryption_upgrade_proof/run.sh +60 -0
  34. data/lib/familia/connection.rb +1 -1
  35. data/lib/familia/core_ext/securerandom.rb +57 -0
  36. data/lib/familia/data_type/database_commands.rb +8 -1
  37. data/lib/familia/data_type/types/sorted_set.rb +4 -2
  38. data/lib/familia/data_type/types/stringkey.rb +10 -3
  39. data/lib/familia/encryption/manager.rb +47 -8
  40. data/lib/familia/encryption/provider.rb +9 -0
  41. data/lib/familia/encryption/providers/aes_gcm_provider.rb +62 -2
  42. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +9 -0
  43. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +10 -0
  44. data/lib/familia/encryption/registry.rb +47 -5
  45. data/lib/familia/encryption/request_cache.rb +18 -1
  46. data/lib/familia/encryption.rb +19 -37
  47. data/lib/familia/features/encrypted_fields/concealed_string.rb +27 -0
  48. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +21 -3
  49. data/lib/familia/features/encrypted_fields.rb +31 -5
  50. data/lib/familia/features/external_identifier.rb +50 -19
  51. data/lib/familia/features/relationships/participation/target_methods.rb +10 -7
  52. data/lib/familia/features/relationships/participation_membership.rb +12 -5
  53. data/lib/familia/horreum/management.rb +13 -0
  54. data/lib/familia/horreum/persistence.rb +58 -16
  55. data/lib/familia/migration/script.rb +12 -1
  56. data/lib/familia/secure_identifier.rb +32 -21
  57. data/lib/familia/settings.rb +66 -8
  58. data/lib/familia/verifiable_identifier.rb +44 -7
  59. data/lib/familia/version.rb +1 -1
  60. data/try/bug_fixes/class_destroy_index_cleanup_try.rb +50 -0
  61. data/try/bug_fixes/permission_query_try.rb +64 -0
  62. data/try/bug_fixes/sorted_set_members_count_try.rb +40 -0
  63. data/try/bug_fixes/stale_unique_index_try.rb +54 -0
  64. data/try/bug_fixes/stringkey_exists_try.rb +153 -0
  65. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +15 -24
  66. data/try/features/encrypted_fields/per_field_algorithm_try.rb +164 -0
  67. data/try/features/encryption/aes_gcm_salt_rotation_try.rb +219 -0
  68. data/try/features/encryption/algorithm_upgrade_try.rb +116 -0
  69. data/try/features/encryption/config_persistence_try.rb +35 -0
  70. data/try/features/encryption/registry_algorithm_resolution_try.rb +125 -0
  71. data/try/features/encryption/request_cache_try.rb +15 -0
  72. data/try/features/external_identifier/external_identifier_try.rb +73 -0
  73. data/try/features/relationships/participation_membership_security_try.rb +64 -0
  74. data/try/integration/verifiable_identifier_try.rb +90 -5
  75. data/try/investigation/memory_leak_proof.rb +415 -0
  76. data/try/investigation/process_memory_leak_proof.rb +205 -0
  77. data/try/support/stress/atomic_write_ownership_stress.rb +152 -0
  78. data/try/thread_safety/atomic_write_ownership_race_try.rb +107 -104
  79. data/try/unit/core/securerandom_polyfill_try.rb +61 -0
  80. data/try/unit/data_types/enumerable_consistency/large_scale_consistency_try.rb +34 -8
  81. data/try/unit/horreum/auto_indexing_on_save_try.rb +9 -8
  82. metadata +35 -3
  83. data/.github/workflows/docs.yml +0 -87
  84. data/changelog.d/20260605_220911_anthropic_sleepy-allen.rst +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7903e14486c85385ad0b682c009b1f7d182e0cff24b107aa0264692529bb8dfd
4
- data.tar.gz: 445219dfcd2df1cf2054b07d90e33902fc21532279730bc274629a2082a02f7f
3
+ metadata.gz: 515d7050ae261290c4e85c6757a68edaa2aae60e21e05b9a6d1fd0457d90fad5
4
+ data.tar.gz: 653efc3da9a21910e7e071ae3065ab06e730334226d8f4820c0ed692fbec8453
5
5
  SHA512:
6
- metadata.gz: 635d35b86d7c6a85332517e3b238b8b932d4823b0cfad2f58b7c349a530de44ae02b91930c9ddac7ed538c619cea3a6f8355eeaba20ebf52358ebd45f4547067
7
- data.tar.gz: d96538d6fbb486fcd92bc329acd52cba07008e40db5df059f338e060cb6dbb4789c8fe0e8e52ee69891662b1b2353eec3832032aa3ccda50848a986d06ea6a4d
6
+ metadata.gz: 9e82443e034e2a2457bdc5366edcc18586d5df1f1ef4e60dc8c50d3d7431850c8899c42e15200d8ad0638455f13f2024a357b5e904f424fc9315312194329cce
7
+ data.tar.gz: 5eed6b2a2017510bac669b43602a1960b0719fa839eb8e61114be27df7010ea4d90eb73abdefc202b86e6c05d0e9dc674087e90e8c828dd649aaad4c446d38a4
@@ -0,0 +1,15 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "github-actions"
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
12
+ - package-ecosystem: "bundler"
13
+ directory: "/" # Location of package manifests
14
+ schedule:
15
+ interval: "weekly"
@@ -23,11 +23,8 @@ jobs:
23
23
  strategy:
24
24
  fail-fast: false
25
25
  matrix:
26
- ruby: ["3.4", "3.5"]
26
+ ruby: ["3.2", "3.3", "3.4", "3.5", "4.0"]
27
27
  continue-on-error: [false]
28
- include:
29
- - ruby: "4.0"
30
- continue-on-error: true
31
28
 
32
29
  services:
33
30
  redis:
@@ -44,7 +41,7 @@ jobs:
44
41
  - 2525:6379
45
42
 
46
43
  steps:
47
- - uses: actions/checkout@v4
44
+ - uses: actions/checkout@v7
48
45
  - name: Set up Ruby
49
46
  uses: ruby/setup-ruby@v1
50
47
  with:
@@ -4,14 +4,34 @@ on:
4
4
  pull_request:
5
5
  types: [opened, synchronize, labeled]
6
6
  workflow_dispatch:
7
+ inputs:
8
+ model:
9
+ description: "Claude model for this run (overrides the CLAUDE_MODEL repo variable)"
10
+ type: choice
11
+ required: false
12
+ default: claude-opus-4-6
13
+ options:
14
+ - claude-opus-4-6
15
+ - claude-sonnet-4-6
16
+ - claude-haiku-4-5-20251001
7
17
 
8
18
  jobs:
9
19
  claude-review:
10
- # Run on opened/synchronize, but only on 'labeled' if label is 'claude-review'
11
- if: |
12
- github.event.action == 'opened' ||
13
- github.event.action == 'synchronize' ||
14
- (github.event.action == 'labeled' && github.event.label.name == 'claude-review')
20
+ # Run only when ALL of the following hold:
21
+ # 1. Author is not a bot (renovate, dependabot, etc. surface as 'name[bot]').
22
+ # 2. PR head is not from a fork. Fork PRs run without repository secrets, so
23
+ # CLAUDE_CODE_OAUTH_TOKEN is empty and the action would fail.
24
+ # 3. The event is a freshly opened PR, a 'claude-review' label add, or a push
25
+ # to a PR that already carries the 'claude-review' label.
26
+ if: ${{
27
+ !endsWith(github.actor, '[bot]') &&
28
+ !github.event.pull_request.head.repo.fork &&
29
+ (
30
+ (github.event.action == 'opened') ||
31
+ (github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
32
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
33
+ )
34
+ }}
15
35
 
16
36
  runs-on: ubuntu-latest
17
37
  permissions:
@@ -22,7 +42,7 @@ jobs:
22
42
 
23
43
  steps:
24
44
  - name: Checkout repository
25
- uses: actions/checkout@v4
45
+ uses: actions/checkout@v7
26
46
  with:
27
47
  fetch-depth: 1
28
48
 
@@ -32,6 +52,13 @@ jobs:
32
52
  with:
33
53
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
34
54
 
55
+ # Primary model precedence: the workflow_dispatch "model" input (manual
56
+ # runs) -> the CLAUDE_MODEL repo variable -> this built-in default.
57
+ # Pinning a current id avoids the action's frozen default, which 404s
58
+ # ("model: claude-sonnet-4-20250514"). Fall back to Sonnet on overload.
59
+ model: "${{ inputs.model || vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
60
+ fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
61
+
35
62
  # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
36
63
  use_sticky_comment: true
37
64
 
@@ -50,6 +77,3 @@ jobs:
50
77
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
51
78
  # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
52
79
  allowed_tools: "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"
53
-
54
- # Allow bot-initiated workflows (e.g., qodo, dependabot)
55
- allowed_bots: "*"
@@ -26,16 +26,23 @@ jobs:
26
26
  actions: read # Required for Claude to read CI results on PRs
27
27
  steps:
28
28
  - name: Checkout repository
29
- uses: actions/checkout@v4
29
+ uses: actions/checkout@v7
30
30
  with:
31
31
  fetch-depth: 1
32
32
 
33
33
  - name: Run Claude Code
34
34
  id: claude
35
- uses: anthropics/claude-code-action@v1
35
+ uses: anthropics/claude-code-action@beta
36
36
  with:
37
37
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
38
 
39
+ # Primary model: set the CLAUDE_MODEL repo variable to override without
40
+ # editing this file. Pinning a current id avoids the action's frozen
41
+ # default, which 404s ("model: claude-sonnet-4-20250514"). Fall back to
42
+ # Sonnet if the primary is unavailable or overloaded.
43
+ model: "${{ vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
44
+ fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
45
+
39
46
  # This is an optional setting that allows Claude to read CI results on PRs
40
47
  additional_permissions: |
41
48
  actions: read
@@ -21,7 +21,7 @@ jobs:
21
21
 
22
22
  steps:
23
23
  - name: Checkout code
24
- uses: actions/checkout@v4
24
+ uses: actions/checkout@v7
25
25
 
26
26
  - name: Set up Ruby
27
27
  uses: ruby/setup-ruby@v1
@@ -91,7 +91,7 @@ jobs:
91
91
 
92
92
  steps:
93
93
  - name: Checkout code
94
- uses: actions/checkout@v4
94
+ uses: actions/checkout@v7
95
95
 
96
96
  - name: Set up Ruby
97
97
  uses: ruby/setup-ruby@v1
@@ -0,0 +1,101 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL Advanced"
13
+
14
+ on:
15
+ push:
16
+ branches: [ "main" ]
17
+ pull_request:
18
+ branches: [ "main" ]
19
+ schedule:
20
+ - cron: '30 0 * * 6'
21
+
22
+ jobs:
23
+ analyze:
24
+ name: Analyze (${{ matrix.language }})
25
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
26
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
27
+ # - https://gh.io/supported-runners-and-hardware-resources
28
+ # - https://gh.io/using-larger-runners (GitHub.com only)
29
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
30
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31
+ permissions:
32
+ # required for all workflows
33
+ security-events: write
34
+
35
+ # required to fetch internal or private CodeQL packs
36
+ packages: read
37
+
38
+ # only required for workflows in private repositories
39
+ actions: read
40
+ contents: read
41
+
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ include:
46
+ - language: actions
47
+ build-mode: none
48
+ - language: ruby
49
+ build-mode: none
50
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51
+ # Use `c-cpp` to analyze code written in C, C++ or both
52
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58
+ steps:
59
+ - name: Checkout repository
60
+ uses: actions/checkout@v7
61
+
62
+ # Add any setup steps before running the `github/codeql-action/init` action.
63
+ # This includes steps like installing compilers or runtimes (`actions/setup-node`
64
+ # or others). This is typically only required for manual builds.
65
+ # - name: Setup runtime (example)
66
+ # uses: actions/setup-example@v1
67
+
68
+ # Initializes the CodeQL tools for scanning.
69
+ - name: Initialize CodeQL
70
+ uses: github/codeql-action/init@v4
71
+ with:
72
+ languages: ${{ matrix.language }}
73
+ build-mode: ${{ matrix.build-mode }}
74
+ # If you wish to specify custom queries, you can do so here or in a config file.
75
+ # By default, queries listed here will override any specified in a config file.
76
+ # Prefix the list here with "+" to use these queries and those in the config file.
77
+
78
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
79
+ # queries: security-extended,security-and-quality
80
+
81
+ # If the analyze step fails for one of the languages you are analyzing with
82
+ # "We were unable to automatically build your code", modify the matrix above
83
+ # to set the build mode to "manual" for that language. Then modify this step
84
+ # to build your code.
85
+ # ℹ️ Command-line programs to run using the OS shell.
86
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
87
+ - name: Run manual build steps
88
+ if: matrix.build-mode == 'manual'
89
+ shell: bash
90
+ run: |
91
+ echo 'If you are using a "manual" build mode for one or more of the' \
92
+ 'languages you are analyzing, replace this with the commands to build' \
93
+ 'your code, for example:'
94
+ echo ' make bootstrap'
95
+ echo ' make release'
96
+ exit 1
97
+
98
+ - name: Perform CodeQL Analysis
99
+ uses: github/codeql-action/analyze@v4
100
+ with:
101
+ category: "/language:${{matrix.language}}"
@@ -0,0 +1,161 @@
1
+ # Release Gem Workflow
2
+ #
3
+ # Automatically builds and publishes the familia gem to RubyGems.org when a
4
+ # GitHub release is published with a semantic version tag (vMAJOR.MINOR.PATCH,
5
+ # e.g. v2.10.1, v3.0.0).
6
+ #
7
+ # Follows the canonical RubyGems Trusted Publishing workflow:
8
+ # https://guides.rubygems.org/trusted-publishing/
9
+ #
10
+ # ============================================================================
11
+ # SETUP INSTRUCTIONS (one-time)
12
+ # ============================================================================
13
+ #
14
+ # This workflow uses RubyGems Trusted Publishing (OIDC). No long-lived API
15
+ # key needs to be stored in GitHub.
16
+ #
17
+ # ----------------------------------------------------------------------------
18
+ # 1. Configure the trusted publisher on RubyGems.org
19
+ # ----------------------------------------------------------------------------
20
+ #
21
+ # a. Sign in to https://rubygems.org and enable MFA on your account
22
+ # (required for trusted publishing).
23
+ #
24
+ # b. Open the gem's trusted publisher page (works for both existing gems
25
+ # and the first-ever publish):
26
+ # Existing gem: https://rubygems.org/gems/familia/trusted_publishers
27
+ # First publish: https://rubygems.org/profile/oidc/pending_trusted_publishers/new
28
+ #
29
+ # c. Click "Create" and fill in:
30
+ # Publisher type: GitHub Actions
31
+ # Repository owner: delano
32
+ # Repository name: familia
33
+ # Workflow filename: release-gem.yml
34
+ # Environment: rubygems.org (must match `environment.name` below)
35
+ #
36
+ # d. Save. RubyGems will now accept short-lived OIDC tokens issued by this
37
+ # workflow running in this repository.
38
+ #
39
+ # ----------------------------------------------------------------------------
40
+ # 2. Configure the GitHub environment
41
+ # ----------------------------------------------------------------------------
42
+ #
43
+ # a. In GitHub: Settings -> Environments -> New environment
44
+ # Name: `rubygems.org` (must match `environment.name` below)
45
+ #
46
+ # b. Under "Deployment branches and tags", restrict to tags matching:
47
+ # v*.*.*
48
+ # This prevents the environment (and its OIDC token) from being used
49
+ # from any branch or non-release tag.
50
+ #
51
+ # c. Optional but recommended: add required reviewers to gate publishes
52
+ # behind manual approval.
53
+ #
54
+ # No GitHub secrets are required - OIDC handles authentication.
55
+ #
56
+ # ----------------------------------------------------------------------------
57
+ # 3. Cutting a release
58
+ # ----------------------------------------------------------------------------
59
+ #
60
+ # a. Bump Familia::VERSION in lib/familia/version.rb (semver: MAJOR.MINOR.PATCH).
61
+ # b. Update CHANGELOG.rst and commit on main.
62
+ # c. On GitHub, draft a Release with tag `vX.Y.Z` (matching the constant)
63
+ # and publish it. This workflow runs automatically: it verifies the tag
64
+ # matches the gemspec version, builds the gem, and pushes it.
65
+ #
66
+ # ----------------------------------------------------------------------------
67
+ # Fallback: API key (only if Trusted Publishing isn't an option)
68
+ # ----------------------------------------------------------------------------
69
+ #
70
+ # 1. Create an API key at https://rubygems.org/profile/api_keys with scope
71
+ # "Push rubygem" restricted to the `familia` gem.
72
+ # 2. Store it as a repo secret named `RUBYGEMS_API_KEY`.
73
+ # 3. Drop the `id-token: write` permission and `environment:` block, and
74
+ # replace the `rubygems/release-gem` step with:
75
+ #
76
+ # - name: Publish to RubyGems
77
+ # env:
78
+ # GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
79
+ # run: gem push familia-*.gem
80
+ #
81
+ # ----------------------------------------------------------------------------
82
+ # Action provenance (SHA pins)
83
+ # ----------------------------------------------------------------------------
84
+ #
85
+ # All third-party actions below are pinned to a full commit SHA, per GitHub's
86
+ # secure-use guidance for release-critical workflows. The accompanying
87
+ # comment records the tag the SHA was resolved from so renovate/dependabot
88
+ # can keep them current.
89
+ #
90
+ # actions/checkout - official GitHub action
91
+ # ruby/setup-ruby - official Ruby org action (GitHub-verified creator)
92
+ # rubygems/release-gem - official RubyGems action for trusted publishing
93
+ #
94
+ # ============================================================================
95
+
96
+ name: Release Gem
97
+
98
+ on:
99
+ release:
100
+ types: [published]
101
+
102
+ # Coarse default. Each job re-declares the narrowest permissions it needs.
103
+ permissions:
104
+ contents: read
105
+
106
+ # Don't allow two release runs to race - one published tag, one publish.
107
+ concurrency:
108
+ group: release-gem-${{ github.event.release.tag_name }}
109
+ cancel-in-progress: false
110
+
111
+ jobs:
112
+ release:
113
+ name: Build and push gem
114
+ runs-on: ubuntu-latest
115
+ timeout-minutes: 10
116
+
117
+ environment:
118
+ name: rubygems.org
119
+ url: https://rubygems.org/gems/familia
120
+
121
+ permissions:
122
+ contents: write # rubygems/release-gem attaches built .gem to the GitHub release
123
+ id-token: write # OIDC token for RubyGems Trusted Publishing
124
+
125
+ steps:
126
+ - name: Checkout
127
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
128
+ with:
129
+ persist-credentials: false
130
+
131
+ - name: Set up Ruby
132
+ uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
133
+ with:
134
+ bundler-cache: true
135
+ # Pinned to 3.3 to match the otto/rhales release builds. The gem is
136
+ # pure Ruby, so the build Ruby is independent of the supported range
137
+ # declared in the gemspec (>= 3.2).
138
+ ruby-version: "3.3"
139
+
140
+ - name: Verify release tag matches Familia::VERSION
141
+ env:
142
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
143
+ run: |
144
+ set -euo pipefail
145
+ case "${RELEASE_TAG}" in
146
+ v[0-9]*.[0-9]*.[0-9]*) ;;
147
+ *)
148
+ echo "Release tag '${RELEASE_TAG}' is not a vMAJOR.MINOR.PATCH semver tag." >&2
149
+ exit 1
150
+ ;;
151
+ esac
152
+ tag_version="${RELEASE_TAG#v}"
153
+ gem_version="$(ruby -r ./lib/familia/version.rb -e 'print Familia::VERSION')"
154
+ if [ "${tag_version}" != "${gem_version}" ]; then
155
+ echo "Release tag ${RELEASE_TAG} (${tag_version}) != Familia::VERSION (${gem_version})." >&2
156
+ exit 1
157
+ fi
158
+ echo "Releasing familia ${gem_version} from tag ${RELEASE_TAG}"
159
+
160
+ - name: Build and push gem to RubyGems
161
+ uses: rubygems/release-gem@6317d8d1f7e28c24d28f6eff169ea854948bd9f7 # v1.2.0
@@ -0,0 +1,66 @@
1
+ # .github/workflows/ruby-lint.yml
2
+
3
+ name: Ruby Lint
4
+
5
+ # Runs RuboCop across the supported Ruby versions. Linting is informational:
6
+ # every dependency/lint step is continue-on-error, so style findings surface as
7
+ # annotations without blocking merges.
8
+
9
+ on:
10
+ push:
11
+ branches:
12
+ - fix/*
13
+ - rel/*
14
+ pull_request:
15
+ branches:
16
+ - main
17
+ - develop
18
+ - feature/*
19
+ workflow_dispatch:
20
+ inputs:
21
+ debug_enabled:
22
+ type: boolean
23
+ description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
24
+ required: false
25
+ default: false
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ lint:
32
+ timeout-minutes: 10 # prevent hung jobs
33
+
34
+ runs-on: ubuntu-24.04
35
+
36
+ strategy:
37
+ fail-fast: true
38
+ matrix:
39
+ ruby: ['3.2', '3.3', '3.4', '3.5', '4.0']
40
+ continue-on-error: [true]
41
+
42
+ steps:
43
+ - name: Checkout code
44
+ uses: actions/checkout@v7
45
+
46
+ - uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: ${{ matrix.ruby }}
49
+ bundler-cache: true
50
+
51
+ - name: Setup tmate session
52
+ uses: mxschmitt/action-tmate@7b6a61a73bbb9793cb80ad69b8dd8ac19261834c # v3
53
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
54
+ with:
55
+ detached: true
56
+
57
+ - name: Install dependencies
58
+ continue-on-error: ${{ matrix.continue-on-error }}
59
+ run: |
60
+ bundle config path vendor/bundle
61
+ bundle install --jobs 4 --retry 3
62
+
63
+ - name: Run Rubocop
64
+ continue-on-error: ${{ matrix.continue-on-error }}
65
+ run: |
66
+ bundle exec rubocop --config .rubocop.yml --format json --fail-level warning
@@ -0,0 +1,110 @@
1
+ name: Generate and Deploy YARD Documentation
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'lib/**/*'
9
+ - 'docs/**/*'
10
+ - 'README.md'
11
+ - 'CHANGELOG.md'
12
+ - 'CHANGELOG.rst'
13
+ - 'LICENSE.txt'
14
+ - '.yardopts'
15
+ - '.github/workflows/yardoc.yml'
16
+ workflow_dispatch:
17
+
18
+ permissions:
19
+ contents: read
20
+ pages: write
21
+ id-token: write
22
+
23
+ # Allow only one concurrent deployment, skipping runs queued between the run
24
+ # in-progress and latest queued. Do NOT cancel in-progress runs so production
25
+ # deployments can complete.
26
+ concurrency:
27
+ group: "pages"
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ build-docs:
32
+ timeout-minutes: 10
33
+ runs-on: ubuntu-latest
34
+ name: Generate YARD Documentation
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v7
39
+ with:
40
+ fetch-depth: 0
41
+
42
+ - name: Set up Ruby environment
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: '3.4'
46
+ bundler-cache: true
47
+
48
+ - name: Generate documentation
49
+ # Uses the repository's committed .yardopts as the single source of
50
+ # truth for the output dir, includes/excludes, markup, and tags.
51
+ run: |
52
+ echo "::group::YARD Documentation Generation"
53
+ bundle exec yard stats --list-undoc || true
54
+ bundle exec yard doc
55
+ echo "::endgroup::"
56
+
57
+ - name: Disable Jekyll processing
58
+ # YARD emits files and directories that begin with underscores; the
59
+ # .nojekyll marker stops GitHub Pages from stripping them.
60
+ run: touch doc/.nojekyll
61
+
62
+ - name: Validate documentation output
63
+ run: |
64
+ echo "::group::Documentation Validation"
65
+ if [ ! -d "doc" ]; then
66
+ echo "Error: documentation directory 'doc' was not generated." >&2
67
+ echo "Ensure .yardopts sets '--output-dir doc'." >&2
68
+ exit 1
69
+ fi
70
+ if [ ! -f "doc/index.html" ]; then
71
+ echo "Warning: doc/index.html not found"
72
+ fi
73
+ echo "Generated HTML files: $(find doc -name '*.html' | wc -l)"
74
+ echo "Total documentation size: $(du -sh doc/ | cut -f1)"
75
+ echo "::endgroup::"
76
+
77
+ - name: Setup GitHub Pages configuration
78
+ uses: actions/configure-pages@v4
79
+
80
+ - name: Upload documentation artifact
81
+ uses: actions/upload-pages-artifact@v5
82
+ with:
83
+ path: './doc'
84
+
85
+ deploy-pages:
86
+ timeout-minutes: 10
87
+ environment:
88
+ name: github-pages
89
+ url: ${{ steps.deployment.outputs.page_url }}
90
+ runs-on: ubuntu-latest
91
+ needs: build-docs
92
+ outputs:
93
+ page_url: ${{ steps.deployment.outputs.page_url }}
94
+
95
+ steps:
96
+ - name: Deploy to GitHub Pages
97
+ id: deployment
98
+ uses: actions/deploy-pages@v5
99
+
100
+ notify-completion:
101
+ timeout-minutes: 5
102
+ runs-on: ubuntu-latest
103
+ needs: [build-docs, deploy-pages]
104
+ if: success()
105
+
106
+ steps:
107
+ - name: Documentation deployment summary
108
+ run: |
109
+ echo "::notice title=Documentation Deployed::YARD documentation successfully deployed to GitHub Pages"
110
+ echo "::notice title=Access URL::Documentation available at: ${{ needs.deploy-pages.outputs.page_url }}"
data/.rubocop.yml CHANGED
@@ -135,9 +135,11 @@ Style/StringLiterals:
135
135
  Enabled: true
136
136
  EnforcedStyle: single_quotes
137
137
 
138
+ # Every lib/ file carries `# frozen_string_literal: true`; require it rather
139
+ # than flag it (the previous `never` contradicted the entire codebase).
138
140
  Style/FrozenStringLiteralComment:
139
141
  Enabled: true
140
- EnforcedStyle: never
142
+ EnforcedStyle: always_true
141
143
 
142
144
  Naming/MemoizedInstanceVariableName:
143
145
  Enabled: false
data/.yardopts CHANGED
@@ -6,7 +6,7 @@
6
6
  --charset utf-8
7
7
  --markup markdown
8
8
  --markup-provider redcarpet
9
- --output-dir public
9
+ --output-dir doc
10
10
  --embed-mixin ClassMethods
11
11
  --embed-mixin InstanceMethods
12
12
  --embed-mixin Features