feat: Add Promotion-Time Attestations for Stella Ops

- Introduced a new document for promotion-time attestations, detailing the purpose, predicate schema, producer workflow, verification flow, APIs, and security considerations.
- Implemented the `stella.ops/promotion@v1` predicate schema to capture promotion evidence including image digest, SBOM/VEX artifacts, and Rekor proof.
- Defined producer responsibilities and workflows for CLI orchestration, signer responsibilities, and Export Center integration.
- Added verification steps for auditors to validate promotion attestations offline.

feat: Create Symbol Manifest v1 Specification

- Developed a specification for Symbol Manifest v1 to provide a deterministic format for publishing debug symbols and source maps.
- Defined the manifest structure, including schema, entries, source maps, toolchain, and provenance.
- Outlined upload and verification processes, resolve APIs, runtime proxy, caching, and offline bundle generation.
- Included security considerations and related tasks for implementation.

chore: Add Ruby Analyzer with Git Sources

- Created a Gemfile and Gemfile.lock for Ruby analyzer with dependencies on git-gem, httparty, and path-gem.
- Implemented main application logic to utilize the defined gems and output their versions.
- Added expected JSON output for the Ruby analyzer to validate the integration of the new gems and their functionalities.
- Developed internal observation classes for Ruby packages, runtime edges, and capabilities, including serialization logic for observations.

test: Add tests for Ruby Analyzer

- Created test fixtures for Ruby analyzer, including Gemfile, Gemfile.lock, main application, and expected JSON output.
- Ensured that the tests validate the correct integration and functionality of the Ruby analyzer with the specified gems.
This commit is contained in:
master
2025-11-11 15:30:22 +02:00
parent 56c687253f
commit c2c6b58b41
56 changed files with 2305 additions and 198 deletions

View File

@@ -1,4 +1,28 @@
[
{
"analyzerId": "ruby",
"componentKey": "observation::ruby",
"name": "Ruby Observation Summary",
"type": "ruby-observation",
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.capability.exec": "true",
"ruby.observation.capability.net": "true",
"ruby.observation.capability.schedulers": "4",
"ruby.observation.capability.serialization": "true",
"ruby.observation.packages": "3",
"ruby.observation.runtime_edges": "3"
},
"evidence": [
{
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022packages\u0022:[{\u0022name\u0022:\u0022custom-gem\u0022,\u0022version\u0022:\u00221.0.0\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022artifact\u0022:\u0022vendor/cache/custom-gem-1.0.0.gem\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022puma\u0022,\u0022version\u0022:\u00226.4.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rake\u0022,\u0022version\u0022:\u002213.1.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022custom-gem\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022puma\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rake\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022capabilities\u0022:{\u0022usesExec\u0022:true,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:true,\u0022jobSchedulers\u0022:[\u0022activejob\u0022,\u0022clockwork\u0022,\u0022resque\u0022,\u0022sidekiq\u0022]}}",
"sha256": "sha256:3818fd050909977a44167565a419a307777bc38998ad49d6a41c054982c6f46e"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/custom-gem@1.0.0",
@@ -8,6 +32,7 @@
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"artifact": "vendor/cache/custom-gem-1.0.0.gem",
"capability.exec": "true",
"capability.net": "true",
"capability.scheduler": "activejob;clockwork;resque;sidekiq",
@@ -16,7 +41,8 @@
"capability.scheduler.resque": "true",
"capability.scheduler.sidekiq": "true",
"capability.serialization": "true",
"declaredOnly": "true",
"declaredOnly": "false",
"groups": "default",
"lockfile": "vendor/cache/custom-gem-1.0.0.gem",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
@@ -27,7 +53,7 @@
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"source": "custom-gem-1.0.0.gem",
"locator": "vendor/cache/custom-gem-1.0.0.gem"
}
]
@@ -50,6 +76,7 @@
"capability.scheduler.sidekiq": "true",
"capability.serialization": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
@@ -83,6 +110,7 @@
"capability.scheduler.sidekiq": "true",
"capability.serialization": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
@@ -98,4 +126,4 @@
}
]
}
]
]

View File

@@ -0,0 +1,12 @@
source "https://rubygems.org"
git "https://github.com/example/git-gem.git", branch: "main" do
gem "git-gem"
end
gem "httparty", "~> 0.21.0"
path "vendor/plugins/path-gem" do
gem "path-gem", "~> 2.1"
end

View File

@@ -0,0 +1,31 @@
GIT
remote: https://github.com/example/git-gem.git
revision: 0123456789abcdef0123456789abcdef01234567
branch: main
specs:
git-gem (0.5.0)
PATH
remote: vendor/plugins/path-gem
specs:
path-gem (2.1.3)
rake (~> 13.0)
GEM
remote: https://rubygems.org/
specs:
httparty (0.21.0)
multi_xml (~> 0.5)
multi_xml (0.6.0)
rake (13.1.0)
PLATFORMS
ruby
DEPENDENCIES
git-gem!
httparty (~> 0.21.0)
path-gem (~> 2.1)!
BUNDLED WITH
2.5.10

View File

@@ -0,0 +1,7 @@
require "git-gem"
require "path-gem"
require "httparty"
puts GitGem.version
puts PathGem::Runner.new.perform
puts HTTParty.get("https://example.invalid")

View File

@@ -0,0 +1,154 @@
[
{
"analyzerId": "ruby",
"componentKey": "observation::ruby",
"name": "Ruby Observation Summary",
"type": "ruby-observation",
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.capability.exec": "false",
"ruby.observation.capability.net": "true",
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.packages": "5",
"ruby.observation.runtime_edges": "3"
},
"evidence": [
{
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022packages\u0022:[{\u0022name\u0022:\u0022git-gem\u0022,\u0022version\u0022:\u00220.5.0\u0022,\u0022source\u0022:\u0022git:https://github.com/example/git-gem.git@0123456789abcdef0123456789abcdef01234567\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022httparty\u0022,\u0022version\u0022:\u00220.21.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022multi_xml\u0022,\u0022version\u0022:\u00220.6.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022path-gem\u0022,\u0022version\u0022:\u00222.1.3\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/cache/path-gem-2.1.3.gem\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rake\u0022,\u0022version\u0022:\u002213.1.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022git-gem\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022httparty\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022path-gem\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[\u0022app/main.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]}}",
"sha256": "sha256:1cd5eb20a226916b9d1acbfc7182845a3ebca8284c7f558b23b7e87395e0a2c2"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/git-gem@0.5.0",
"purl": "pkg:gem/git-gem@0.5.0",
"name": "git-gem",
"version": "0.5.0",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"capability.net": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "git:https://github.com/example/git-gem.git@0123456789abcdef0123456789abcdef01234567"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/httparty@0.21.0",
"purl": "pkg:gem/httparty@0.21.0",
"name": "httparty",
"version": "0.21.0",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"capability.net": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/multi_xml@0.6.0",
"purl": "pkg:gem/multi_xml@0.6.0",
"name": "multi_xml",
"version": "0.6.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"capability.net": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/path-gem@2.1.3",
"purl": "pkg:gem/path-gem@2.1.3",
"name": "path-gem",
"version": "2.1.3",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"artifact": "vendor/cache/path-gem-2.1.3.gem",
"capability.net": "true",
"declaredOnly": "false",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app/main.rb",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "vendor-cache"
},
"evidence": [
{
"kind": "file",
"source": "path-gem-2.1.3.gem",
"locator": "vendor/cache/path-gem-2.1.3.gem"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/rake@13.1.0",
"purl": "pkg:gem/rake@13.1.0",
"name": "rake",
"version": "13.1.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"capability.net": "true",
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
}
]

View File

@@ -1 +1,193 @@
[]
[
{
"analyzerId": "ruby",
"componentKey": "observation::ruby",
"name": "Ruby Observation Summary",
"type": "ruby-observation",
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.capability.exec": "false",
"ruby.observation.capability.net": "false",
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.packages": "7",
"ruby.observation.runtime_edges": "4"
},
"evidence": [
{
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022packages\u0022:[{\u0022name\u0022:\u0022api-gem\u0022,\u0022version\u0022:\u00220.1.0\u0022,\u0022source\u0022:\u0022apps\u0022,\u0022declaredOnly\u0022:false,\u0022artifact\u0022:\u0022apps/api/vendor/bundle/ruby/3.1.0/gems/api-gem-0.1.0\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022bootsnap\u0022,\u0022version\u0022:\u00221.18.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022apps/api/Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022pry\u0022,\u0022version\u0022:\u00221.0.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022development\u0022,\u0022test\u0022]},{\u0022name\u0022:\u0022puma\u0022,\u0022version\u0022:\u00226.4.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022console\u0022,\u0022production\u0022]},{\u0022name\u0022:\u0022rails\u0022,\u0022version\u0022:\u00227.1.3\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rubocop\u0022,\u0022version\u0022:\u00221.60.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022development\u0022,\u0022test\u0022]},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022version\u0022:\u00227.2.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022apps/api/Gemfile.lock\u0022,\u0022groups\u0022:[\u0022jobs\u0022]}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022bootsnap\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022puma\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rails\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sidekiq\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]}}",
"sha256": "sha256:6f9996b97be3dbbf3a18c2cb91624d45ddd16b2a374dd4a7f48049f5192114e2"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/api-gem@0.1.0",
"purl": "pkg:gem/api-gem@0.1.0",
"name": "api-gem",
"version": "0.1.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"artifact": "apps/api/vendor/bundle/ruby/3.1.0/gems/api-gem-0.1.0",
"declaredOnly": "false",
"groups": "default",
"lockfile": "apps/api/vendor/bundle/ruby/3.1.0/gems/api-gem-0.1.0",
"source": "apps"
},
"evidence": [
{
"kind": "file",
"source": "api-gem-0.1.0",
"locator": "apps/api/vendor/bundle/ruby/3.1.0/gems/api-gem-0.1.0"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/bootsnap@1.18.4",
"purl": "pkg:gem/bootsnap@1.18.4",
"name": "bootsnap",
"version": "1.18.4",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "apps/api/Gemfile.lock",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "apps/api/Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/pry@1.0.0",
"purl": "pkg:gem/pry@1.0.0",
"name": "pry",
"version": "1.0.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "development;test",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/puma@6.4.2",
"purl": "pkg:gem/puma@6.4.2",
"name": "puma",
"version": "6.4.2",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "console;production",
"lockfile": "Gemfile.lock",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/rails@7.1.3",
"purl": "pkg:gem/rails@7.1.3",
"name": "rails",
"version": "7.1.3",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/rubocop@1.60.0",
"purl": "pkg:gem/rubocop@1.60.0",
"name": "rubocop",
"version": "1.60.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "development;test",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/sidekiq@7.2.4",
"purl": "pkg:gem/sidekiq@7.2.4",
"name": "sidekiq",
"version": "7.2.4",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "jobs",
"lockfile": "apps/api/Gemfile.lock",
"runtime.files": "app/main.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "apps/api/Gemfile.lock"
}
]
}
]

View File

@@ -34,4 +34,19 @@ public sealed class RubyLanguageAnalyzerTests
new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() },
cancellationToken: TestContext.Current.CancellationToken);
}
[Fact]
public async Task GitAndPathSourcesAsync()
{
var fixture = TestPaths.ResolveFixture("lang", "ruby", "git-sources");
var golden = Path.Combine(fixture, "expected.json");
var usageHints = new LanguageUsageHints(new[] { Path.Combine(fixture, "app", "main.rb") });
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixture,
golden,
new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() },
cancellationToken: TestContext.Current.CancellationToken,
usageHints: usageHints);
}
}