diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9aefdee --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Xquik Contributors +# SPDX-License-Identifier: MIT + +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 diff --git a/.github/workflows/context7-refresh.yml b/.github/workflows/context7-refresh.yml index b43e5cd..c51d298 100644 --- a/.github/workflows/context7-refresh.yml +++ b/.github/workflows/context7-refresh.yml @@ -20,7 +20,8 @@ permissions: {} jobs: refresh: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - name: Trigger Context7 refresh env: @@ -35,7 +36,7 @@ jobs: body_file="$(mktemp)" trap 'rm -f "$body_file"' EXIT - status="$(curl -sS -o "$body_file" -w "%{http_code}" -X POST "https://context7.com/api/v1/refresh" \ + status="$(curl --connect-timeout 10 --max-time 30 --proto '=https' -sS --tlsv1.2 -o "$body_file" -w "%{http_code}" -X POST "https://context7.com/api/v1/refresh" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${CONTEXT7_API_KEY}" \ -d '{"libraryName": "/xquik-dev/.github"}')" diff --git a/.github/workflows/public-contract.yml b/.github/workflows/public-contract.yml index 871a239..15a44dc 100644 --- a/.github/workflows/public-contract.yml +++ b/.github/workflows/public-contract.yml @@ -11,32 +11,44 @@ on: paths: - ".github/workflows/public-contract.yml" - "ASSURANCE.md" + - "BUILD_DEBUG.md" - "CODE_OF_CONDUCT.md" - "CONTRIBUTING.md" + - "DISCOVERY.md" - "GOVERNANCE.md" - "OPENSSF.md" + - "REGRESSION_TESTS.md" - "RELEASES.md" + - "REVIEW_EVIDENCE.md" - "REVIEWING.md" - "ROADMAP.md" - "SECURITY.md" - "profile/README.md" - "scripts/check-public-contract.mjs" + - "scripts/check-review-evidence.mjs" + - "scripts/public-projects.mjs" schedule: - cron: "17 7 * * 1" workflow_dispatch: permissions: contents: read + pull-requests: read jobs: check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 3 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: 22 + node-version: 22.23.1 - run: node scripts/check-public-contract.mjs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: node scripts/check-review-evidence.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-attestations.yml b/.github/workflows/release-attestations.yml new file mode 100644 index 0000000..ffcafe7 --- /dev/null +++ b/.github/workflows/release-attestations.yml @@ -0,0 +1,85 @@ +# Copyright the Xquik contributors. +# SPDX-License-Identifier: MIT + +name: Verify Release Attestations + +on: + pull_request: + paths: + - ".github/workflows/release-attestations.yml" + push: + branches: + - main + paths: + - ".github/workflows/release-attestations.yml" + schedule: + - cron: "43 7 * * 1" + workflow_dispatch: + +permissions: {} + +concurrency: + group: release-attestations-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Verify ${{ matrix.project }} + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + attestations: read + contents: read + strategy: + fail-fast: false + matrix: + include: + - project: "C# SDK" + repository: Xquik-dev/x-twitter-scraper-csharp + artifact_template: XTwitterScraper.__VERSION__.nupkg + bare_version: "true" + signer_workflow: Xquik-dev/x-twitter-scraper-csharp/.github/workflows/publish-nuget.yml + - project: Go SDK + repository: Xquik-dev/x-twitter-scraper-go + artifact_template: x-twitter-scraper-go-__VERSION__.zip + bare_version: "false" + signer_workflow: Xquik-dev/x-twitter-scraper-go/.github/workflows/release-provenance.yml + - project: PHP SDK + repository: Xquik-dev/x-twitter-scraper-php + artifact_template: x-twitter-scraper-php-__VERSION__.zip + bare_version: "false" + signer_workflow: Xquik-dev/x-twitter-scraper-php/.github/workflows/release-provenance.yml + steps: + - name: Download Latest Release Artifact + env: + ARTIFACT_TEMPLATE: ${{ matrix.artifact_template }} + BARE_VERSION: ${{ matrix.bare_version }} + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ matrix.repository }} + run: | + set -euo pipefail + release_tag="$(gh release view --repo "$REPOSITORY" --json tagName --jq .tagName)" + artifact_version="$release_tag" + if [[ "$BARE_VERSION" == "true" ]]; then + artifact_version="${release_tag#v}" + fi + artifact_name="${ARTIFACT_TEMPLATE/__VERSION__/$artifact_version}" + mkdir artifact + gh release download "$release_tag" \ + --repo "$REPOSITORY" \ + --pattern "$artifact_name" \ + --dir artifact + test -f "artifact/$artifact_name" + echo "ARTIFACT=artifact/$artifact_name" >> "$GITHUB_ENV" + + - name: Verify Repository-Bound Provenance + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ matrix.repository }} + SIGNER_WORKFLOW: ${{ matrix.signer_workflow }} + run: | + set -euo pipefail + gh attestation verify "$ARTIFACT" \ + --repo "$REPOSITORY" \ + --signer-workflow "$SIGNER_WORKFLOW" \ + --deny-self-hosted-runners diff --git a/BUILD_DEBUG.md b/BUILD_DEBUG.md new file mode 100644 index 0000000..aa75f4f --- /dev/null +++ b/BUILD_DEBUG.md @@ -0,0 +1,82 @@ +# Debug-Information Preservation Evidence + +This register supports the OpenSSF Gold `build_preserve_debug` criterion. + +The snapshot date is July 25, 2026. + +This register covers all 17 standalone software projects. + +OpenSSF asks build systems to preserve requested debug information. + +See the official [Gold criterion](https://www.bestpractices.dev/en/criteria?details=true&rationale=true#build_preserve_debug). + +Do not count pending changes as default-branch evidence. + +## Current Assessment + +| Project | Result | Public Evidence | Preserved Debug Context | +| --- | --- | --- | --- | +| `hermes-tweet` | Met | [Python package configuration](https://github.com/Xquik-dev/hermes-tweet/blob/master/pyproject.toml) | The wheel preserves Python sources. No stripping step exists. | +| `n8n-nodes-xquik` | Met | [TypeScript build configuration](https://github.com/Xquik-dev/n8n-nodes-xquik/blob/main/tsconfig.json) | TypeScript emits source maps into the published `dist` directory. | +| `paperclip-plugin-xquik` | Met | [esbuild configuration](https://github.com/Xquik-dev/paperclip-plugin-xquik/blob/main/esbuild.config.mjs) | esbuild emits source maps with both published bundles. | +| `prefect-xquik` | Met | [Python package configuration](https://github.com/Xquik-dev/prefect-xquik/blob/main/pyproject.toml) | The wheel preserves Python sources. No stripping step exists. | +| `terraform-provider-x-twitter-scraper` | Met | [Release configuration](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/blob/main/.goreleaser.yml) and [debug check](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/blob/main/bin/check-release-debug-info) | GoReleaser retains DWARF data. Required CI verifies the release binary. | +| `tweetclaw` | Met | [TypeScript build configuration](https://github.com/Xquik-dev/tweetclaw/blob/master/tsconfig.json) | TypeScript emits source maps into the published `dist` directory. | +| `x-twitter-scraper` | Met | [Published package configuration](https://github.com/Xquik-dev/x-twitter-scraper/blob/master/package.json) | npm installs source assets directly. No stripping step exists. | +| `x-twitter-scraper-cli` | Met | [Release configuration](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/.goreleaser.yml) and [debug check](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/bin/check-release-debug-info) | GoReleaser retains DWARF data. Required CI verifies the release binary. | +| `x-twitter-scraper-csharp` | Met | [.NET build properties](https://github.com/Xquik-dev/x-twitter-scraper-csharp/blob/main/src/Directory.Build.props) | Release packages include symbols with embedded debug information. | +| `x-twitter-scraper-go` | Met | [Go build check](https://github.com/Xquik-dev/x-twitter-scraper-go/blob/main/scripts/lint) | `go build` honors `GOFLAGS` and does not strip output. | +| `x-twitter-scraper-java` | Met | [JVM build configuration](https://github.com/Xquik-dev/x-twitter-scraper-java/blob/main/buildSrc/src/main/kotlin/x-twitter-scraper.java.gradle.kts) | Gradle preserves source and line tables in JVM classes. | +| `x-twitter-scraper-kotlin` | Met | [JVM build configuration](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/blob/main/buildSrc/src/main/kotlin/x-twitter-scraper.java.gradle.kts) | Gradle preserves source and line tables in JVM classes. | +| `x-twitter-scraper-php` | Met | [Composer package configuration](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/composer.json) | Composer installs PHP sources directly. No stripping step exists. | +| `x-twitter-scraper-python` | Met | [Python package configuration](https://github.com/Xquik-dev/x-twitter-scraper-python/blob/main/pyproject.toml) | The wheel preserves Python sources. No stripping step exists. | +| `x-twitter-scraper-ruby` | Met | [Ruby package configuration](https://github.com/Xquik-dev/x-twitter-scraper-ruby/blob/main/x_twitter_scraper.gemspec) | The gem preserves Ruby sources. No stripping step exists. | +| `x-twitter-scraper-typescript` | Met | [TypeScript build configuration](https://github.com/Xquik-dev/x-twitter-scraper-typescript/blob/main/tsconfig.build.json) | TypeScript emits source maps beside published JavaScript. | +| `xquik-haystack` | Met | [Python package configuration](https://github.com/Xquik-dev/xquik-haystack/blob/main/pyproject.toml) | The wheel preserves Python sources. No stripping step exists. | + +## Native Release Verification + +The Terraform provider and CLI publish native Go binaries. + +Their release configurations previously passed `-s -w` to the Go linker. + +Those flags removed symbol and DWARF data. + +Each remediation now performs these checks: + +1. Build the release binary with GoReleaser. +2. Select the Linux AMD64 binary from `dist/artifacts.json`. +3. Inspect its section table with `objdump`. +4. Require both `.debug_info` and `.debug_line`. +5. Repeat the check before publishing. + +The checks keep `-trimpath` for reproducibility. + +## JVM Verification + +Java and Kotlin release JARs retain JVM debugging attributes. + +Rebuild either project and inspect a main class: + +```bash +./gradlew build +javap -l -classpath PATH_TO_JAR FULLY_QUALIFIED_CLASS +``` + +The output must contain `LineNumberTable`. + +The class metadata also retains its source filename. + +## Source Package Verification + +Python, PHP, and Ruby releases distribute language source files. + +Their package builds do not compile or strip native binaries. + +The JavaScript and TypeScript projects preserve source context. + +They either ship source assets or publish source maps. + +Review this register after build or packaging changes. + +Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 675f912..57f2226 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,18 @@ Thanks for helping improve public Xquik SDKs, docs, skills, plugins, examples, a - Browse issues labeled `help wanted` for broader contributions. - Ask for clarification before starting unclear or overlapping work. +## Grow With a Project + +Small tasks are a starting point. + +Read the selected repository's roadmap before proposing sustained work. + +Build useful contributions across focused, reviewed pull requests. + +Never split work or inflate metrics to qualify for recognition. + +Link merged work in the [OpenSSF human evidence tracker](https://github.com/Xquik-dev/.github/issues/3). + ## Before Opening an Issue or PR - Use the affected repository when possible. diff --git a/OPENSSF.md b/OPENSSF.md index 14c3d72..39cc1fb 100644 --- a/OPENSSF.md +++ b/OPENSSF.md @@ -51,14 +51,13 @@ Use each live entry for current status and submitted evidence. | `x-twitter-scraper-typescript` | [OpenSSF project 13740](https://www.bestpractices.dev/projects/13740) | | `xquik-haystack` | [OpenSSF project 13741](https://www.bestpractices.dev/projects/13741) | -## July 24, 2026 Audit Snapshot +## July 25, 2026 Audit Snapshot - Xquik-dev has 19 public repositories. - Seventeen repositories are standalone software projects. - All 17 live entries report Passing at 100%. -- All 17 entries report Silver completion at 84%. -- Fifteen entries report Gold completion at 35%. -- `n8n-nodes-xquik` and `x-twitter-scraper` report Gold completion at 48%. +- Silver completion is 98% for every project. +- Gold completion ranges from 65% to 78%. - All 55 Silver and 23 Gold fields have explicit classifications. - No Silver or Gold field remains unknown. - Every required Silver explanation and evidence URL is present. @@ -66,13 +65,12 @@ Use each live entry for current status and submitted evidence. - Each entry includes dated remediation guidance for those requirements. - All 17 entries mark cryptographic 2FA as Met. - All 17 projects expose a scoped newcomer task. -- Fourteen projects have verified signed release artifacts. -- Three projects need project-controlled release signature evidence. +- All 17 projects have verified signed release artifacts. - All 17 software projects protect `v*` tags against destructive updates. - All latest public Git tags remain unsigned. -- Latest required checks pass on every ready remediation pull request. -- All 19 ready remediation pull requests are mergeable. -- All 22 open pull requests have zero unresolved review threads. +- All 15 projects with qualifying fixes meet the six-month regression ratio. +- Two projects have no qualifying fixes and use `N/A`. +- All 17 projects meet the current 50% two-person-review threshold. ## Shared Evidence @@ -84,6 +82,9 @@ These documents support repository-specific badge evidence: - [Vulnerability response process](SECURITY.md) - [Architecture and security assurance](ASSURANCE.md) - [Release integrity and verification](RELEASES.md) +- [Debug-information preservation evidence](BUILD_DEBUG.md) +- [Six-month regression-test evidence](REGRESSION_TESTS.md) +- [Two-person review evidence](REVIEW_EVIDENCE.md) - [Code of Conduct](CODE_OF_CONDUCT.md) - [Twelve-month roadmap](ROADMAP.md) @@ -120,7 +121,7 @@ These tasks satisfy the Gold `small_tasks` evidence requirement. ## Verified Organization Controls - GitHub requires two-factor authentication for organization members. -- The sole maintainer prefers a configured passkey for GitHub 2FA. +- The organization owner prefers a configured passkey for GitHub 2FA. - An authenticator app provides an additional cryptographic 2FA method. - SMS is not configured as a GitHub 2FA method. - Every public repository supports private vulnerability reporting. @@ -132,45 +133,55 @@ These tasks satisfy the Gold `small_tasks` evidence requirement. - Seventeen standalone projects have current Passing badges. - Seventeen standalone projects run required CI checks. -## Current Remediation Pull Requests - -These pull requests implement project-specific and shared-site technical remediation. - -Passing checks prove implementation, not independent review or default-branch adoption. - -| Repository | Pull Request | Verified Scope | -| --- | --- | --- | -| `.github` | [#2](https://github.com/Xquik-dev/.github/pull/2) | Shared governance, badge inventory, and public contract verification | -| `hermes-tweet` | [#610](https://github.com/Xquik-dev/hermes-tweet/pull/610) | License notices, tests, coverage, and package checks | -| `n8n-nodes-xquik` | [#11](https://github.com/Xquik-dev/n8n-nodes-xquik/pull/11) | License notices, tests, security checks, and reproducible packages | -| `paperclip-plugin-xquik` | [#4](https://github.com/Xquik-dev/paperclip-plugin-xquik/pull/4) | License notices, tests, security checks, and reproducible packages | -| `prefect-xquik` | [#15](https://github.com/Xquik-dev/prefect-xquik/pull/15) | License notices, tests, security checks, and reproducible packages | -| `terraform-provider-x-twitter-scraper` | [#11](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/pull/11) | 93.6% statements, 82.45% branches, race-safe JSON encoding, BSD notices, security, and reproducible releases | -| `tweetclaw` | [#21](https://github.com/Xquik-dev/tweetclaw/pull/21) | Security boundaries, tests, coverage, and public disclosures | -| `x-twitter-scraper` | [#19](https://github.com/Xquik-dev/x-twitter-scraper/pull/19) | Coverage, licensing, security, reproducibility, and public disclosures | -| `x-twitter-scraper-cli` | [#13](https://github.com/Xquik-dev/x-twitter-scraper-cli/pull/13) | 91.2% statements, 80.09% branches, BSD notices, portability, and reproducible releases | -| `x-twitter-scraper-csharp` | [#19](https://github.com/Xquik-dev/x-twitter-scraper-csharp/pull/19) | 94.28% lines, 89.20% branches, security fixes, licensing, and reproducible packages | -| `x-twitter-scraper-go` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-go/pull/14) | Coverage, race and fuzz tests, licensing, security, and reproducibility | -| `x-twitter-scraper-java` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-java/pull/14) | 90.52% maintained lines, 80.77% branches, fuzzing, security, licensing, reproducibility, and signed Maven releases | -| `x-twitter-scraper-kotlin` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/pull/14) | 91.06% maintained lines, 80.56% branches, fuzzing, security, licensing, reproducibility, and signed Maven releases | -| `x-twitter-scraper-php` | [#12](https://github.com/Xquik-dev/x-twitter-scraper-php/pull/12) | 99.18% executable lines, 83.07% branches, security fixes, licensing, and reproducible packages | -| `x-twitter-scraper-python` | [#18](https://github.com/Xquik-dev/x-twitter-scraper-python/pull/18) | 94.19% statements, 80.89% branches, licensing, security, and reproducible packages | -| `x-twitter-scraper-ruby` | [#17](https://github.com/Xquik-dev/x-twitter-scraper-ruby/pull/17) | Bounded RubyGems release verification | -| `x-twitter-scraper-typescript` | [#15](https://github.com/Xquik-dev/x-twitter-scraper-typescript/pull/15) | Coverage, licensing, security checks, and reproducible packages | -| `xquik-haystack` | [#4](https://github.com/Xquik-dev/xquik-haystack/pull/4) | License notices, tests, and coverage | -| `xquik-docs` | [#17](https://github.com/Xquik-dev/xquik-docs/pull/17) | Shared-site classification, 282 tests, dependency integrity, licensing, security, and static validation | - -Each pull request requires a different human reviewer before merging. +## Technical Baseline Adoption + +These pull requests establish project-specific and shared technical controls. + +| Repository | Pull Request | Status | Verified Scope | +| --- | --- | --- | --- | +| `.github` | [#2](https://github.com/Xquik-dev/.github/pull/2) | Merged | Shared governance, badge inventory, and public contract verification | +| `hermes-tweet` | [#610](https://github.com/Xquik-dev/hermes-tweet/pull/610) | Merged | License notices, tests, coverage, and package checks | +| `n8n-nodes-xquik` | [#11](https://github.com/Xquik-dev/n8n-nodes-xquik/pull/11) | Merged | License notices, tests, security checks, and reproducible packages | +| `paperclip-plugin-xquik` | [#4](https://github.com/Xquik-dev/paperclip-plugin-xquik/pull/4) | Merged | License notices, tests, security checks, and reproducible packages | +| `prefect-xquik` | [#15](https://github.com/Xquik-dev/prefect-xquik/pull/15) | Merged | License notices, tests, security checks, and reproducible packages | +| `terraform-provider-x-twitter-scraper` | [#11](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/pull/11) | Merged | Coverage, race safety, notices, security, and reproducible releases | +| `tweetclaw` | [#21](https://github.com/Xquik-dev/tweetclaw/pull/21) | Merged | Security boundaries, tests, coverage, and public disclosures | +| `x-twitter-scraper` | [#19](https://github.com/Xquik-dev/x-twitter-scraper/pull/19) | Merged | Coverage, licensing, security, reproducibility, and disclosures | +| `x-twitter-scraper-cli` | [#13](https://github.com/Xquik-dev/x-twitter-scraper-cli/pull/13) | Merged | Coverage, notices, portability, and reproducible releases | +| `x-twitter-scraper-csharp` | [#19](https://github.com/Xquik-dev/x-twitter-scraper-csharp/pull/19) | Merged | Coverage, security fixes, licensing, and reproducible packages | +| `x-twitter-scraper-go` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-go/pull/14) | Merged | Coverage, race tests, fuzzing, licensing, security, and reproducibility | +| `x-twitter-scraper-java` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-java/pull/14) | Merged | Maintained coverage, fuzzing, security, licensing, and reproducibility | +| `x-twitter-scraper-kotlin` | [#14](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/pull/14) | Merged | Generated-inclusive coverage, fuzzing, security, licensing, and reproducibility | +| `x-twitter-scraper-php` | [#12](https://github.com/Xquik-dev/x-twitter-scraper-php/pull/12) | Merged | Coverage, security fixes, licensing, and reproducible packages | +| `x-twitter-scraper-python` | [#18](https://github.com/Xquik-dev/x-twitter-scraper-python/pull/18) | Merged | Coverage, licensing, security, and reproducible packages | +| `x-twitter-scraper-ruby` | [#17](https://github.com/Xquik-dev/x-twitter-scraper-ruby/pull/17) | Merged | Coverage, licensing, reproducibility, and release verification | +| `x-twitter-scraper-typescript` | [#15](https://github.com/Xquik-dev/x-twitter-scraper-typescript/pull/15) | Merged | Coverage, licensing, security, and reproducible packages | +| `xquik-haystack` | [#4](https://github.com/Xquik-dev/xquik-haystack/pull/4) | Merged | License notices, regression tests, coverage, and reproducibility | +| `xquik-docs` | [#17](https://github.com/Xquik-dev/xquik-docs/pull/17) | Merged | Shared-site assurance, tests, integrity, licensing, and security | + +All 19 baseline pull requests have merged. Do not count pending changes as default-branch evidence. -The Java and Kotlin SDKs publish generated-inclusive coverage. +Java [#17](https://github.com/Xquik-dev/x-twitter-scraper-java/pull/17) +records 94.05% generated-inclusive line coverage and 92.81% branch coverage. -Java reports 41.31% lines and 21.79% branches. +Kotlin #14 records 92.70% generated-inclusive line coverage and 91.87% branch coverage. -Kotlin reports 39.19% lines and 20.54% branches. +TypeScript [#18](https://github.com/Xquik-dev/x-twitter-scraper-typescript/pull/18) +adds 2 regression guards. -Generated-code coverage applicability remains an explicit assessment gap. +Hermes [#612](https://github.com/Xquik-dev/hermes-tweet/pull/612) +and Paperclip [#6](https://github.com/Xquik-dev/paperclip-plugin-xquik/pull/6) +add reproducible-package evidence. + +All 17 projects now have default-branch debug-preservation evidence. + +Terraform [#14](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/pull/14) +and CLI [#16](https://github.com/Xquik-dev/x-twitter-scraper-cli/pull/16) +merged independently reviewed verification. + +See [debug-information evidence](BUILD_DEBUG.md) for the complete assessment. ## Other Open Pull Request Audit @@ -191,14 +202,7 @@ All 19 public repository READMEs were audited on July 24, 2026. All default branches contain the approved independence disclosure. -Fifteen default branches contain the exact unformatted footer. - -Four pending pull requests add that exact footer: - -- [Terraform provider #11](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/pull/11) -- [TweetClaw #21](https://github.com/Xquik-dev/tweetclaw/pull/21) -- [Core Skill and MCP package #19](https://github.com/Xquik-dev/x-twitter-scraper/pull/19) -- [CLI #13](https://github.com/Xquik-dev/x-twitter-scraper-cli/pull/13) +All 19 default branches contain the exact unformatted footer. All repository descriptions identify their supported purpose. @@ -208,7 +212,9 @@ Topics remain specific to supported languages, workflows, and customer tasks. Every public repository now includes the `xquik` discovery topic. -Descriptions contain 94 to 185 characters. +Descriptions contain 94 to 140 characters. + +Each opening sentence contains 9 to 14 words. Each repository uses 14 to 20 accurate topics. @@ -227,37 +233,29 @@ They do not require keyword lists or duplicate questions. ## Known Gold Gaps -The organization currently has one member. +A second reviewer has repository write access. -It does not yet meet this required Silver prerequisite: +Public evidence still does not prove full release and legal continuity. -- Access continuity after the loss of the sole organization member. +The projects do not yet meet this required Silver prerequisite: -It does not yet meet these human Gold requirements: +- Continuity after the loss of any one project member. + +They do not yet meet these human Gold requirements: - A bus factor of 2 or more. - Two unassociated significant contributors per project. -- Verifiable independent review for 50% of released modifications. - A human security review completed within the last 5 years. Open remediation pull requests cannot satisfy these human requirements. Track human prerequisites in [organization issue #3](https://github.com/Xquik-dev/.github/issues/3). +Track the maintainer nomination in [organization issue #8](https://github.com/Xquik-dev/.github/issues/8). + Track scoped human reviews in [organization issue #5](https://github.com/Xquik-dev/.github/issues/5). -These Silver and Gold areas still need default-branch evidence: - -- Repository-specific architecture and assurance cases. -- Repeatable and reproducible builds. -- A 6-month regression-test ratio. -- Statement coverage of at least 80% and 90%. -- Secure-design and input-validation evidence. -- Signed releases and public verification instructions. -- Per-file copyright and license notices on default branches. -- Branch coverage when a suitable FLOSS tool exists. -- Dynamic analysis before major releases. -- Requested debug-information preservation where applicable. +All 17 projects now have project-controlled release signature evidence. Never mark a criterion as met without current public evidence. diff --git a/README.md b/README.md index f978641..dae4cf3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ Public GitHub profile and community defaults for Xquik SDKs, docs, and agent int - Code review: [REVIEWING.md](REVIEWING.md) - Public roadmap: [ROADMAP.md](ROADMAP.md) - OpenSSF evidence: [OPENSSF.md](OPENSSF.md) +- Debug-information evidence: [BUILD_DEBUG.md](BUILD_DEBUG.md) +- Six-month regression evidence: [REGRESSION_TESTS.md](REGRESSION_TESTS.md) +- Two-person review evidence: [REVIEW_EVIDENCE.md](REVIEW_EVIDENCE.md) - Discovery and README policy: [DISCOVERY.md](DISCOVERY.md) - Code of Conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) diff --git a/REGRESSION_TESTS.md b/REGRESSION_TESTS.md new file mode 100644 index 0000000..c8cfa34 --- /dev/null +++ b/REGRESSION_TESTS.md @@ -0,0 +1,329 @@ +# Six-Month Regression-Test Evidence + +This register supports the OpenSSF Gold `regression_tests_added50` criterion. + +The snapshot date is July 25, 2026. +The review window starts January 24, 2026. + +## Counting Rules + +Count one defect for each distinct, project-owned root cause. +Use a public fix, pull request, issue, or changelog as evidence. +Count fixes made after a public release. + +Include defects affecting shipped code, packages, installation, or builds. +Exclude features, refactors, documentation-only changes, and CI-only maintenance. +Exclude dependency updates without a demonstrated project defect. +Exclude imported upstream history before Xquik adopted the project. + +Count a regression when an automated check detects the defect's return. +The check must run through the repository's documented test or validation path. +Later regression coverage may protect an earlier fix. + +Use `N/A` only when the six-month denominator is zero. +Do not count pending pull-request evidence until merge. + +## Current Ratios + +| Project | Fixed Bugs | Guarded Bugs | Ratio | Default-Branch Result | +| --- | ---: | ---: | ---: | --- | +| `hermes-tweet` | 2 | 2 | 100% | Met | +| `n8n-nodes-xquik` | 0 | 0 | N/A | N/A | +| `paperclip-plugin-xquik` | 0 | 0 | N/A | N/A | +| `prefect-xquik` | 1 | 1 | 100% | Met | +| `terraform-provider-x-twitter-scraper` | 2 | 2 | 100% | Met | +| `tweetclaw` | 3 | 2 | 66.7% | Met | +| `x-twitter-scraper` | 10 | 10 | 100% | Met | +| `x-twitter-scraper-cli` | 8 | 8 | 100% | Met | +| `x-twitter-scraper-csharp` | 3 | 3 | 100% | Met | +| `x-twitter-scraper-go` | 2 | 2 | 100% | Met | +| `x-twitter-scraper-java` | 1 | 1 | 100% | Met | +| `x-twitter-scraper-kotlin` | 2 | 2 | 100% | Met | +| `x-twitter-scraper-php` | 4 | 4 | 100% | Met | +| `x-twitter-scraper-python` | 2 | 2 | 100% | Met | +| `x-twitter-scraper-ruby` | 5 | 4 | 80% | Met | +| `x-twitter-scraper-typescript` | 3 | 3 | 100% | Met | +| `xquik-haystack` | 1 | 1 | 100% | Met | + +## Project Evidence + +### Hermes Tweet + +Count these 2 fixes: + +- [Restore the marketplace grade](https://github.com/Xquik-dev/hermes-tweet/pull/595). +- [Align the agent-readable catalog](https://github.com/Xquik-dev/hermes-tweet/pull/608). + +These tests protect both fixes: + +- [Marketplace contract tests](https://github.com/Xquik-dev/hermes-tweet/blob/master/tests/test_metadata.py). +- [Catalog behavior tests](https://github.com/Xquik-dev/hermes-tweet/blob/master/tests/test_catalog.py). +- [Public-surface safety tests](https://github.com/Xquik-dev/hermes-tweet/blob/master/tests/test_public_safety.py). + +The ratio is 2 of 2. + +### n8n Nodes + +No qualifying released product defect was fixed during this window. +Changes affected features, dependencies, documentation, or release automation. + +The ratio is `N/A`. + +### Paperclip Plugin + +No qualifying released product defect was fixed during this window. +The deprecated action change only affected the CI workflow. + +The ratio is `N/A`. + +### Prefect Collection + +Count the [default API host fix](https://github.com/Xquik-dev/prefect-xquik/commit/fa1b8db660f9c2530dc8c4c140618b34d16c6216). + +These tests preserve the public host: + +- [Client default-host test](https://github.com/Xquik-dev/prefect-xquik/blob/main/tests/test_client.py). +- [Credentials default-host test](https://github.com/Xquik-dev/prefect-xquik/blob/main/tests/test_credentials.py). + +The ratio is 1 of 1. + +### Terraform Provider + +Count these 2 fixes: + +- [Remove the stale integration surface](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/commit/8fe251402e2f2170a8b0f222bf19d44241dc1cc1). +- [Make the JSON cache concurrency-safe](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/commit/7b3e09a8e87fe5a380a0809b8bf97e0495b6adfe). + +These tests protect both fixes: + +- [Registered provider-surface tests](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/blob/main/internal/provider_coverage_test.go). +- [JSON concurrency regression](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/blob/main/internal/apijson/concurrency_test.go). + +The ratio is 2 of 2. + +### TweetClaw + +Count these 3 fixes: + +- [Eliminate package backup races](https://github.com/Xquik-dev/tweetclaw/pull/18). +- [Fix public API contract drift](https://github.com/Xquik-dev/tweetclaw/pull/23). +- [Patch the Hono Node server](https://github.com/Xquik-dev/tweetclaw/pull/24). + +These checks protect the first 2 fixes: + +- [Package artifact validator](https://github.com/Xquik-dev/tweetclaw/blob/master/scripts/check-package-artifact.mjs). +- [API specification tests](https://github.com/Xquik-dev/tweetclaw/blob/master/tests/api-spec.test.ts). +- [Request contract tests](https://github.com/Xquik-dev/tweetclaw/blob/master/tests/request.test.ts). + +The dependency-only Hono patch lacks a focused regression. +The ratio is 2 of 3. + +### Skill & Plugin Bundle + +Count 10 shipped package defects: + +- [Package audit surfaces](https://github.com/Xquik-dev/x-twitter-scraper/commit/56bad11881326fdcc7e593dbf2b263e16fb9607d). +- [Skill audit surfaces](https://github.com/Xquik-dev/x-twitter-scraper/commit/aba2900e83ab37e44c193465189d9b96920ac2af). +- [Skill safety metadata](https://github.com/Xquik-dev/x-twitter-scraper/commit/7936541f7605bf826a0da01df0e08604804405af). +- [Skill references](https://github.com/Xquik-dev/x-twitter-scraper/commit/55d103756e8a1d50aceccef7d96229efe072701c). +- [Claude plugin MCP configuration](https://github.com/Xquik-dev/x-twitter-scraper/commit/da7a6d1bdbfdf5df8c78dff9554e2b8fadd103d5). +- [Skill frontmatter](https://github.com/Xquik-dev/x-twitter-scraper/commit/3ab22fb664cc2a0cb3a5da450a29dafbc2fd8573). +- [Skill bundle correctness](https://github.com/Xquik-dev/x-twitter-scraper/commit/ac7c9f1a1f5e6798c69907e15e15effdbc8a3046). +- [Marketplace validation](https://github.com/Xquik-dev/x-twitter-scraper/commit/8a94746f45260c3a08dcbc42ca2d03a046359fb1). +- [Public MCP contract](https://github.com/Xquik-dev/x-twitter-scraper/pull/16). +- [MCP registry metadata](https://github.com/Xquik-dev/x-twitter-scraper/pull/17). + +These tests protect all 10 package contracts: + +- [Package guard integration test](https://github.com/Xquik-dev/x-twitter-scraper/blob/master/tests/package-guard.test.mjs). +- [Frontmatter parser tests](https://github.com/Xquik-dev/x-twitter-scraper/blob/master/tests/frontmatter.test.mjs). +- [Local server contract tests](https://github.com/Xquik-dev/x-twitter-scraper/blob/master/tests/stub-server.test.mjs). + +The ratio is 10 of 10. + +### CLI + +Count 4 released formatting defects: + +- Empty explore results. +- Raw JSON item iteration. +- Raw output with transforms. +- Non-TTY explore fallback. + +The [CLI changelog](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/CHANGELOG.md) records these fixes. + +Count 4 later correctness defects: + +- Custom exit handling. +- Wrapped Unix connection closure. +- Typed empty environment values. +- Signed and unsigned 8-bit query encoding. + +[OpenSSF quality-gate PR #13](https://github.com/Xquik-dev/x-twitter-scraper-cli/pull/13) records these fixes. + +These tests protect all 8 defects: + +- [Explorer regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/internal/jsonview/explorer_test.go). +- [Output formatting regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/pkg/cmd/cmdutil_test.go). +- [Unix connection regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/pkg/cmd/cmdutil_unix_test.go). +- [Environment parsing regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/internal/requestflag/requestflag_test.go). +- [Query encoding regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/internal/apiquery/coverage_test.go). +- [Exit handling regressions](https://github.com/Xquik-dev/x-twitter-scraper-cli/blob/main/cmd/x-twitter-scraper/main_test.go). + +The ratio is 8 of 8. + +### C# SDK + +Count these 3 runtime defects: + +- Response enumeration ignored cancellation. +- The default `HttpClient` timeout conflicted with SDK timeouts. +- Null-only scorer weights failed deserialization. + +The [C# changelog](https://github.com/Xquik-dev/x-twitter-scraper-csharp/blob/main/CHANGELOG.md) records these fixes. + +These tests protect all 3 defects: + +- [HTTP response cancellation tests](https://github.com/Xquik-dev/x-twitter-scraper-csharp/blob/main/src/XTwitterScraper.Tests/Core/HttpResponseTest.cs). +- [Client timeout tests](https://github.com/Xquik-dev/x-twitter-scraper-csharp/blob/main/src/XTwitterScraper.Tests/Core/ClientOptionsTest.cs). +- [Compose response tests](https://github.com/Xquik-dev/x-twitter-scraper-csharp/blob/main/src/XTwitterScraper.Tests/Models/Compose/ComposeCreateResponseTest.cs). + +The ratio is 3 of 3. + +### Go SDK + +Count these 2 fixes: + +- [Resolve the generated import cycle](https://github.com/Xquik-dev/x-twitter-scraper-go/commit/4188d03924c0b7c9072ec4095efe54bf9056d094). +- [Make diagnostic dumps concurrency-safe](https://github.com/Xquik-dev/x-twitter-scraper-go/commit/6c35deb74fab127bcde9947c0a915e90dbad067b). + +These tests protect both fixes: + +- [Generated model contract tests](https://github.com/Xquik-dev/x-twitter-scraper-go/blob/main/model_json_test.go). +- [Diagnostic concurrency tests](https://github.com/Xquik-dev/x-twitter-scraper-go/blob/main/internal/apierror/apierror_test.go). + +The ratio is 2 of 2. + +### Java SDK + +Count the [Java 26 shrinker fix](https://github.com/Xquik-dev/x-twitter-scraper-java/commit/00d54a6b8045c97ba497910960c6004a48cff90d). + +The [ProGuard compatibility test](https://github.com/Xquik-dev/x-twitter-scraper-java/blob/main/x-twitter-scraper-java-proguard-test/src/test/kotlin/com/x_twitter_scraper/api/proguard/ProGuardCompatibilityTest.kt) protects it. + +The ratio is 1 of 1. + +### Kotlin SDK + +Count these 2 fixes: + +- [Kotlin 26 shrinker compatibility](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/commit/62b77cbcf226a5940491daee18ed7f496609f2c4). +- [Generated SDK buildability](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/commit/a2ff6517753db2724fbdbdd3b98a76171f222c39). + +These tests protect both fixes: + +- [ProGuard compatibility test](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/blob/main/x-twitter-scraper-kotlin-proguard-test/src/test/kotlin/com/x_twitter_scraper/api/proguard/ProGuardCompatibilityTest.kt). +- [Compose request tests](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/blob/main/x-twitter-scraper-kotlin-core/src/test/kotlin/com/x_twitter_scraper/api/models/compose/ComposeCreateParamsTest.kt). +- [Compose response tests](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/blob/main/x-twitter-scraper-kotlin-core/src/test/kotlin/com/x_twitter_scraper/api/models/compose/ComposeCreateResponseTest.kt). + +The ratio is 2 of 2. + +### PHP SDK + +Count 4 released runtime defects: + +- Guzzle streaming activation. +- File parameter generation. +- Union and enum serialization. +- Enum-typed property hydration. + +The [PHP changelog](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/CHANGELOG.md) records these fixes. + +These tests protect all 4 defects: + +- [Streaming transport tests](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/tests/Core/StreamingTransportTest.php). +- [Streaming client tests](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/tests/Core/StreamingHttpClientTest.php). +- [File parameter tests](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/tests/Core/FileParamTest.php). +- [Conversion tests](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/tests/Core/ConversionTest.php). +- [Model serialization tests](https://github.com/Xquik-dev/x-twitter-scraper-php/blob/main/tests/Core/ModelTest.php). + +The ratio is 4 of 4. + +### Python SDK + +Count 2 released runtime defects: + +- Preserve hardcoded query parameters. +- Send file data once. + +The [Python changelog](https://github.com/Xquik-dev/x-twitter-scraper-python/blob/main/CHANGELOG.md) records these fixes. + +These tests protect both defects: + +- [Query parameter regressions](https://github.com/Xquik-dev/x-twitter-scraper-python/blob/main/tests/test_client.py). +- [File extraction regressions](https://github.com/Xquik-dev/x-twitter-scraper-python/blob/main/tests/test_extract_files.py). + +The ratio is 2 of 2. + +### Ruby SDK + +Count 5 released runtime or installation defects: + +- RFC 3986 path encoding. +- A runtime variable typo. +- Bodyless request content types. +- Ruby 4 `base64` installation. +- Content-type parsing. + +The [Ruby changelog](https://github.com/Xquik-dev/x-twitter-scraper-ruby/blob/main/CHANGELOG.md) records these fixes. + +These checks protect 4 defects: + +- [Client request regressions](https://github.com/Xquik-dev/x-twitter-scraper-ruby/blob/main/test/x_twitter_scraper/client_test.rb). +- [Content-type regressions](https://github.com/Xquik-dev/x-twitter-scraper-ruby/blob/main/test/x_twitter_scraper/internal/util_test.rb). +- [Edge-case regressions](https://github.com/Xquik-dev/x-twitter-scraper-ruby/blob/main/test/x_twitter_scraper/internal/edge_cases_test.rb). +- Ruby 4 CI exercises the declared runtime dependency. + +The historical variable typo lacks a focused regression. +The ratio is 4 of 5. + +### TypeScript SDK + +Count 3 released package defects: + +- Guest wallet Bearer authentication. +- Missing npm bug-tracker metadata. +- The stale integrations resource. + +The [TypeScript changelog](https://github.com/Xquik-dev/x-twitter-scraper-typescript/blob/main/CHANGELOG.md) records these fixes. + +[Client authentication tests](https://github.com/Xquik-dev/x-twitter-scraper-typescript/blob/main/tests/index.test.ts) protect the first defect. + +[PR #18](https://github.com/Xquik-dev/x-twitter-scraper-typescript/pull/18) protects the other 2 defects. + +The ratio is 3 of 3. + +### Haystack Integration + +Count the [installed Twine invocation fix](https://github.com/Xquik-dev/xquik-haystack/commit/1b49376f0d7b62b4feffe4c64a1f5cefe4959f0f). + +[PR #4](https://github.com/Xquik-dev/xquik-haystack/pull/4) adds its regression assertion. + +The ratio is 1 of 1. + +## Shared Project Sites + +The `.github` repository provides shared organization evidence. +The `xquik-docs` repository provides shared project documentation. + +Neither repository has an independent release lifecycle. +Neither repository has a separate OpenSSF project entry. +Their fixes belong to the affected software projects. + +## Maintenance + +Recalculate this register after each qualifying bug fix. +Recalculate it monthly when work remains active. +Move the start date forward with each snapshot. +Keep merged evidence separate from pending evidence. + +Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. diff --git a/RELEASES.md b/RELEASES.md index d30aa6f..7490c57 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,7 +10,7 @@ Verify the signer identity, repository, artifact digest, and expected workflow. ## Evidence Status -Assessment date: July 24, 2026. +Assessment date: July 25, 2026. | Projects | Distribution | Current Public Evidence | | --- | --- | --- | @@ -20,17 +20,16 @@ Assessment date: July 24, 2026. | `x-twitter-scraper-cli` | GitHub Releases | GitHub SLSA provenance | | `x-twitter-scraper-ruby` | RubyGems | Sigstore bundle bound to the published gem | | `x-twitter-scraper-java`, `x-twitter-scraper-kotlin` | Maven Central | OpenPGP signatures for every published Maven file | -| `x-twitter-scraper-csharp` | NuGet | Project-controlled signature evidence remains incomplete | -| `x-twitter-scraper-go` | Go modules | Project-controlled signature evidence remains incomplete | -| `x-twitter-scraper-php` | Packagist | Project-controlled signature evidence remains incomplete | +| `x-twitter-scraper-csharp` | NuGet and GitHub Releases | Repository-bound SLSA provenance for the exact `.nupkg` | +| `x-twitter-scraper-go` | Go modules and GitHub Releases | Repository-bound SLSA provenance for the source archive | +| `x-twitter-scraper-php` | Packagist and GitHub Releases | Repository-bound SLSA provenance for the Composer archive | -The first 14 projects have verifiable signed release artifacts. +All 17 projects have verifiable signed release artifacts. -Their Silver badge answers still require default-branch documentation. +Keep each badge answer aligned with current default-branch evidence. -The remaining 3 projects must add public cryptographic release evidence. - -Track that work in [organization issue #4](https://github.com/Xquik-dev/.github/issues/4). +The [consumer verification workflow](.github/workflows/release-attestations.yml) +checks the latest C#, Go, and PHP artifacts weekly. ## Verify npm Provenance @@ -82,14 +81,14 @@ RubyGems publishes a Sigstore bundle for the current gem. Download the gem and its bundle: ```sh -gem_file=x-twitter-scraper-0.5.4.gem +gem_file=x-twitter-scraper-0.5.5.gem bundle_file="$gem_file.sigstore.json" curl --fail --location --output "$gem_file" \ "https://rubygems.org/downloads/$gem_file" curl --fail --location \ - https://rubygems.org/api/v1/attestations/x-twitter-scraper-0.5.4.json \ + https://rubygems.org/api/v1/attestations/x-twitter-scraper-0.5.5.json \ | jq '.[0]' > "$bundle_file" ``` @@ -98,7 +97,7 @@ Verify the exact workflow identity: ```sh gem exec sigstore-cli:0.2.3 verify \ --bundle="$bundle_file" \ - --certificate-identity=https://github.com/Xquik-dev/x-twitter-scraper-ruby/.github/workflows/publish-gem.yml@refs/tags/v0.5.4 \ + --certificate-identity=https://github.com/Xquik-dev/x-twitter-scraper-ruby/.github/workflows/publish-gem.yml@refs/tags/v0.5.5 \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ "$gem_file" ``` @@ -108,7 +107,7 @@ Require an `OK` result for the downloaded artifact. The verified SHA-256 digest is: ```text -ce55622baf95df9b6599db33a7a1627463be3735b891e93177a67f8875d3aaa8 +6dfdcabd408a330d80ef87f4e650aca0004ba8a0eb8b49cb92e06a97a7cf5502 ``` ## Verify Maven Central Signatures @@ -119,8 +118,9 @@ Download an artifact and its signature: ```sh project=java -artifact="x-twitter-scraper-$project-0.5.2.jar" -base="https://repo.maven.apache.org/maven2/com/xquik/api/x-twitter-scraper-$project/0.5.2" +version=0.6.0 +artifact="x-twitter-scraper-$project-$version.jar" +base="https://repo.maven.apache.org/maven2/com/xquik/api/x-twitter-scraper-$project/$version" curl --fail --location --remote-name "$base/$artifact" curl --fail --location --remote-name "$base/$artifact.asc" @@ -134,7 +134,7 @@ gpg --keyserver hkps://keyserver.ubuntu.com \ gpg --verify "$artifact.asc" "$artifact" ``` -Set `project=kotlin` to verify the Kotlin SDK. +Set `project=kotlin` and `version=0.5.3` to verify Kotlin. Confirm this full fingerprint before trusting the key: @@ -145,16 +145,22 @@ Confirm this full fingerprint before trusting the key: The verified root JAR SHA-256 digests are: ```text -Java: 87a9b770f16b32d016fac4220a5ec626d32871e692d9b7bc8d261be8e0a58e89 -Kotlin: 912278aac18d6e78b0f56e790eb3cdb387d1bf30e7b188086bc86095b9201734 +Java: 17aaf5366ab6ad65869e5fb4f92acb2351bb08e0a12a0d4fcab6de8875193916 +Kotlin: 6e083f62f50231bbce09b99c6c6fe5b8f5ade1babaefac8220d80897df93cdf1 ``` -The audit verified 30 files across 6 Maven components. +The audit verified 15 Java artifacts and their signatures. + +They span 3 Maven components. + +It also verified 15 Kotlin artifacts and their signatures. + +They span 3 Maven components. -Consumer CI independently verified both root artifacts: +Public workflows verified both root artifacts after registry publication: -- [Java release verification](https://github.com/Xquik-dev/x-twitter-scraper-java/actions/runs/30076440647) -- [Kotlin release verification](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/actions/runs/30076440671) +- [Java release verification](https://github.com/Xquik-dev/x-twitter-scraper-java/actions/runs/30110262525) +- [Kotlin release verification](https://github.com/Xquik-dev/x-twitter-scraper-kotlin/actions/runs/30125831619) ## Verify GitHub Attestations @@ -176,6 +182,50 @@ Confirm the subject digest matches the downloaded artifact. For stricter policy, also require the expected signer workflow. +## Verify Project-Controlled SDK Artifacts + +Three SDK ecosystems also publish canonical GitHub release artifacts. + +Download and verify each artifact: + +```sh +gh release download v0.5.4 \ + --repo Xquik-dev/x-twitter-scraper-csharp \ + --pattern XTwitterScraper.0.5.4.nupkg +gh attestation verify XTwitterScraper.0.5.4.nupkg \ + --repo Xquik-dev/x-twitter-scraper-csharp \ + --signer-workflow Xquik-dev/x-twitter-scraper-csharp/.github/workflows/publish-nuget.yml \ + --deny-self-hosted-runners + +gh release download v0.7.0 \ + --repo Xquik-dev/x-twitter-scraper-go \ + --pattern x-twitter-scraper-go-v0.7.0.zip +gh attestation verify x-twitter-scraper-go-v0.7.0.zip \ + --repo Xquik-dev/x-twitter-scraper-go \ + --signer-workflow Xquik-dev/x-twitter-scraper-go/.github/workflows/release-provenance.yml \ + --deny-self-hosted-runners + +gh release download v0.6.0 \ + --repo Xquik-dev/x-twitter-scraper-php \ + --pattern x-twitter-scraper-php-v0.6.0.zip +gh attestation verify x-twitter-scraper-php-v0.6.0.zip \ + --repo Xquik-dev/x-twitter-scraper-php \ + --signer-workflow Xquik-dev/x-twitter-scraper-php/.github/workflows/release-provenance.yml \ + --deny-self-hosted-runners +``` + +The verified SHA-256 digests are: + +```text +C#: 7bef1ec1688b472424d7e92738342a446abfa6a9b1d314c4cd66fff919b5f34f +Go: a59bd116af5ff6cc911c38b2fd515559d5f97b3eeb489d1a6148fd13fb459fb0 +PHP: 31fdf66d8cb1d0d8aeacbb8748189029eafc8b178b905057fd35540f5a01589b +``` + +Each certificate identifies its Xquik-dev release workflow. + +Each certificate also binds the artifact to its matching release tag. + ## Verify Terraform Checksums Download the checksum file, its signature, and the selected archive. @@ -206,7 +256,7 @@ Every standalone repository has an active ruleset for `v*` tags. All 17 rulesets block deletion and non-fast-forward updates. -The Terraform provider also restricts creation to its approved release actor. +The rulesets currently allow tag creation. These rules preserve published tag identity. diff --git a/REVIEWING.md b/REVIEWING.md index 6d738fd..55c7f8c 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -88,8 +88,10 @@ A different reviewer must approve the fix before public release. Maintainers must review the modification history before each major release. -At least 50% of released modifications require independent author review. +At least 50% of released modifications require non-author human review. -The organization does not claim this threshold without verifiable pull request evidence. +The [two-person review ledger](REVIEW_EVIDENCE.md) records current evidence. + +The weekly verifier counts direct commits as unreviewed. Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. diff --git a/REVIEW_EVIDENCE.md b/REVIEW_EVIDENCE.md new file mode 100644 index 0000000..c236bf3 --- /dev/null +++ b/REVIEW_EVIDENCE.md @@ -0,0 +1,80 @@ +# Two-Person Review Evidence + +This ledger records OpenSSF Gold `two_person_review` evidence. + +It covers every standalone Xquik-dev project. + +The `.github` and `xquik-docs` repositories provide shared community and documentation surfaces. + +They do not publish standalone software releases. + +The project criterion does not apply to them separately. + +## Audit Window + +The review policy took effect on July 23, 2026. + +The audit includes every default-branch commit since `2026-07-23T00:00:00Z`. + +It uses these conservative rules: + +- Count every default-branch commit as one modification. +- Count direct commits as unreviewed. +- Count each rebased commit when GitHub associates it with an approved pull request. +- Count a squash merge as one reviewed modification. +- Require a known author and approval from another human. +- Require that approval on the exact pull request head. +- Require the approval before the pull request merged. +- Ignore bot comments, automated reviews, dismissed reviews, and pending changes. + +See the [code review policy](REVIEWING.md). + +## July 25, 2026 Result + +All 17 projects meet the required 50% threshold. + +| Repository | Reviewed Commits | Total Commits | Ratio | +| --- | ---: | ---: | ---: | +| `hermes-tweet` | 5 | 7 | 71.4% | +| `n8n-nodes-xquik` | 4 | 5 | 80.0% | +| `paperclip-plugin-xquik` | 3 | 4 | 75.0% | +| `prefect-xquik` | 4 | 5 | 80.0% | +| `terraform-provider-x-twitter-scraper` | 13 | 14 | 92.9% | +| `tweetclaw` | 2 | 3 | 66.7% | +| `x-twitter-scraper` | 5 | 6 | 83.3% | +| `x-twitter-scraper-cli` | 12 | 13 | 92.3% | +| `x-twitter-scraper-csharp` | 10 | 12 | 83.3% | +| `x-twitter-scraper-go` | 9 | 10 | 90.0% | +| `x-twitter-scraper-java` | 14 | 15 | 93.3% | +| `x-twitter-scraper-kotlin` | 4 | 5 | 80.0% | +| `x-twitter-scraper-php` | 13 | 14 | 92.9% | +| `x-twitter-scraper-python` | 9 | 10 | 90.0% | +| `x-twitter-scraper-ruby` | 7 | 8 | 87.5% | +| `x-twitter-scraper-typescript` | 7 | 10 | 70.0% | +| `xquik-haystack` | 3 | 4 | 75.0% | + +The audit intentionally retains unreviewed commits in every denominator. + +## Reproduce the Audit + +Run the verifier with a GitHub token that can read public pull requests: + +```sh +GITHUB_TOKEN=... node scripts/check-review-evidence.mjs +``` + +The [public contract workflow](.github/workflows/public-contract.yml) runs it weekly. + +The verifier fails if any project falls below 50%. + +It also fails when the public repository inventory changes. + +## Maintenance + +- Keep exact-head review dismissal enabled. +- Require a non-author approval before every non-trivial merge. +- Review the ledger before each release. +- Update the audit window only with documented policy evidence. +- Never remove unreviewed commits to improve a ratio. + +Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. diff --git a/scripts/check-public-contract.mjs b/scripts/check-public-contract.mjs index 68aef34..b54d061 100644 --- a/scripts/check-public-contract.mjs +++ b/scripts/check-public-contract.mjs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: MIT import { readFile } from "node:fs/promises"; +import { GITHUB_ORG, OPENSSF_PROJECT_IDS } from "./public-projects.mjs"; const GITHUB_API = "https://api.github.com"; -const GITHUB_ORG = "Xquik-dev"; const OPENAPI_URL = "https://xquik.com/openapi.json"; const SERVER_CARD_URL = "https://xquik.com/.well-known/mcp/server-card.json"; const HTTP_METHODS = new Set(["delete", "get", "head", "options", "patch", "post", "put", "trace"]); @@ -31,6 +31,16 @@ const COMMUNITY_POLICY_REQUIREMENTS = new Map([ "Pending changes do not count as default-branch evidence.", ], ], + [ + "BUILD_DEBUG.md", + [ + "17 standalone software projects", + "Do not count pending changes as default-branch evidence.", + "## Native Release Verification", + "## JVM Verification", + "## Source Package Verification", + ], + ], ["CODE_OF_CONDUCT.md", ["## Enforcement", "support@xquik.com"]], [ "CONTRIBUTING.md", @@ -68,10 +78,25 @@ const COMMUNITY_POLICY_REQUIREMENTS = new Map([ "## Verify PyPI Attestations", "## Verify RubyGems Attestations", "## Verify GitHub Attestations", - "ce55622baf95df9b6599db33a7a1627463be3735b891e93177a67f8875d3aaa8", + "## Verify Project-Controlled SDK Artifacts", + "6dfdcabd408a330d80ef87f4e650aca0004ba8a0eb8b49cb92e06a97a7cf5502", + "17aaf5366ab6ad65869e5fb4f92acb2351bb08e0a12a0d4fcab6de8875193916", + "7bef1ec1688b472424d7e92738342a446abfa6a9b1d314c4cd66fff919b5f34f", + "a59bd116af5ff6cc911c38b2fd515559d5f97b3eeb489d1a6148fd13fb459fb0", + "31fdf66d8cb1d0d8aeacbb8748189029eafc8b178b905057fd35540f5a01589b", "Keep `version_tags_signed` Unmet", ], ], + [ + "REVIEW_EVIDENCE.md", + [ + "The review policy took effect on July 23, 2026.", + "The `.github` and `xquik-docs` repositories provide shared community and documentation surfaces.", + "Count direct commits as unreviewed.", + "Require a known author and approval from another human.", + "All 17 projects meet the required 50% threshold.", + ], + ], ["ROADMAP.md", ["July 2027", "OpenSSF Best Practices"]], [ "SECURITY.md", @@ -290,6 +315,13 @@ async function checkRepoReadmes(repos) { if (!visible.includes(INDEPENDENCE_NOTICE)) { throw new Error(`${repo.name}/${path} is missing the approved independence notice.`); } + const projectId = OPENSSF_PROJECT_IDS.get(repo.name); + if (projectId !== undefined) { + const badgeUrl = `https://www.bestpractices.dev/projects/${projectId}/badge`; + if (!readme.includes(badgeUrl)) { + throw new Error(`${repo.name}/${path} is missing OpenSSF badge ${projectId}.`); + } + } const stale = STALE_PUBLIC_COPY.find((pattern) => pattern.test(visible)); if (stale) throw new Error(`${repo.name}/${path} contains stale public copy: ${stale}.`); }), @@ -300,12 +332,20 @@ function checkRepoDiscovery(repos) { for (const repo of repos) { const description = repo.description?.trim() ?? ""; const topics = repo.topics ?? []; - if (description.length < 70 || description.length > 200) { + if (description.length < 94 || description.length > 140) { throw new Error(`${repo.name} has an unclear repository description length.`); } if (!description.endsWith(REPOSITORY_DESCRIPTION_NOTICE)) { throw new Error(`${repo.name} is missing the compact independence notice.`); } + const purpose = description + .slice(0, -REPOSITORY_DESCRIPTION_NOTICE.length) + .replace(/\.\s*$/u, "") + .trim(); + const purposeWords = purpose.split(/\s+/u).length; + if (purposeWords < 9 || purposeWords > 14) { + throw new Error(`${repo.name} must use 9-14 words before its independence notice.`); + } if (!repo.homepage?.startsWith("https://")) { throw new Error(`${repo.name} needs an HTTPS homepage.`); } @@ -334,6 +374,18 @@ async function checkCommunityPolicies() { ); } +async function checkOpenSsfInventory() { + const evidence = await readFile("OPENSSF.md", "utf8"); + for (const [repo, projectId] of OPENSSF_PROJECT_IDS) { + const entry = + `| \`${repo}\` | [OpenSSF project ${projectId}]` + + `(https://www.bestpractices.dev/projects/${projectId}) |`; + if (!evidence.includes(entry)) { + throw new Error(`OPENSSF.md is missing ${repo} project ${projectId}.`); + } + } +} + async function checkIntegrationSurfaces(openApi, reposByName) { await Promise.all( INTEGRATION_SURFACES.map(async (surface) => { @@ -407,6 +459,7 @@ if (/126 operations|40\+ agents|118 operations through 2 tools/u.test(profile)) await Promise.all([ checkCommunityPolicies(), + checkOpenSsfInventory(), checkIntegrationSurfaces(openApi, reposByName), checkRepoReadmes(repos), ]); diff --git a/scripts/check-review-evidence.mjs b/scripts/check-review-evidence.mjs new file mode 100644 index 0000000..d8acce5 --- /dev/null +++ b/scripts/check-review-evidence.mjs @@ -0,0 +1,197 @@ +// Copyright the Xquik contributors. +// SPDX-License-Identifier: MIT + +import { + GITHUB_ORG, + OPENSSF_PROJECT_NAMES, + SHARED_REPOSITORY_NAMES, +} from "./public-projects.mjs"; + +const GITHUB_GRAPHQL = "https://api.github.com/graphql"; +const GITHUB_REST = "https://api.github.com"; +const REVIEW_START = "2026-07-23T00:00:00Z"; +const REQUIRED_RATIO = 0.5; +const REVIEW_QUERY = ` + query ReviewEvidence( + $owner: String! + $name: String! + $since: GitTimestamp! + $after: String + ) { + repository(owner: $owner, name: $name) { + defaultBranchRef { + target { + ... on Commit { + history(first: 100, after: $after, since: $since) { + nodes { + oid + associatedPullRequests(first: 10) { + nodes { + number + author { + __typename + login + } + headRefOid + mergedAt + reviews(first: 100) { + nodes { + author { + __typename + login + } + commit { + oid + } + state + submittedAt + } + } + } + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + } + } + } +`; + +function githubToken() { + const token = process.env.GITHUB_TOKEN; + if (!token) throw new Error("GITHUB_TOKEN is required."); + return token; +} + +function githubHeaders() { + return { + accept: "application/vnd.github+json", + authorization: `Bearer ${githubToken()}`, + "user-agent": "xquik-review-evidence-check", + }; +} + +async function graphql(variables) { + const response = await fetch(GITHUB_GRAPHQL, { + method: "POST", + headers: { + ...githubHeaders(), + "content-type": "application/json", + }, + body: JSON.stringify({ query: REVIEW_QUERY, variables }), + }); + if (!response.ok) { + throw new Error(`GitHub GraphQL returned HTTP ${response.status}.`); + } + const payload = await response.json(); + if (payload.errors?.length) { + throw new Error(payload.errors.map(({ message }) => message).join("; ")); + } + return payload.data; +} + +async function loadPublicRepoNames() { + const names = []; + for (let page = 1; ; page += 1) { + const response = await fetch( + `${GITHUB_REST}/orgs/${GITHUB_ORG}/repos?type=public&per_page=100&page=${page}`, + { headers: githubHeaders() }, + ); + if (!response.ok) { + throw new Error(`GitHub REST returned HTTP ${response.status}.`); + } + const repositories = await response.json(); + if (!Array.isArray(repositories)) { + throw new Error("GitHub REST returned an invalid repository inventory."); + } + names.push(...repositories.map(({ name }) => name)); + if (repositories.length < 100) return names; + } +} + +async function verifyPublicInventory() { + const expected = new Set([...SHARED_REPOSITORY_NAMES, ...OPENSSF_PROJECT_NAMES]); + const actual = new Set(await loadPublicRepoNames()); + const unexpected = [...actual].filter((name) => !expected.has(name)); + const missing = [...expected].filter((name) => !actual.has(name)); + if (unexpected.length === 0 && missing.length === 0) return; + throw new Error( + `Public repository inventory changed. Unexpected: ${unexpected.join(", ") || "none"}. ` + + `Missing: ${missing.join(", ") || "none"}.`, + ); +} + +function hasTwoPersonReview(commit) { + return commit.associatedPullRequests.nodes.some((pullRequest) => { + if (!pullRequest.mergedAt || !pullRequest.headRefOid) return false; + const author = pullRequest.author?.login; + if (!author) return false; + return pullRequest.reviews.nodes.some( + (review) => + review.state === "APPROVED" && + review.author?.__typename === "User" && + review.author.login !== author && + review.commit?.oid === pullRequest.headRefOid && + review.submittedAt <= pullRequest.mergedAt, + ); + }); +} + +async function loadCommits(project) { + const commits = []; + let after = null; + do { + const data = await graphql({ + owner: GITHUB_ORG, + name: project, + since: REVIEW_START, + after, + }); + const history = data.repository?.defaultBranchRef?.target?.history; + if (!history) throw new Error(`${project} has no readable default-branch history.`); + commits.push(...history.nodes); + after = history.pageInfo.hasNextPage ? history.pageInfo.endCursor : null; + } while (after !== null); + return commits; +} + +async function auditProject(project) { + const commits = await loadCommits(project); + if (commits.length === 0) { + throw new Error(`${project} has no commits in the review window.`); + } + const reviewed = commits.filter(hasTwoPersonReview); + return { + project, + reviewed: reviewed.length, + total: commits.length, + ratio: reviewed.length / commits.length, + }; +} + +await verifyPublicInventory(); + +const results = await Promise.all(OPENSSF_PROJECT_NAMES.map(auditProject)); +const failures = results.filter(({ ratio }) => ratio < REQUIRED_RATIO); + +for (const { project, ratio, reviewed, total } of results) { + process.stdout.write( + `${project}: ${reviewed}/${total} commits reviewed by a non-author human ` + + `(${(ratio * 100).toFixed(1)}%).\n`, + ); +} + +if (failures.length > 0) { + throw new Error( + `Independent review fell below 50% for: ${failures + .map(({ project }) => project) + .join(", ")}.`, + ); +} + +process.stdout.write(`All ${results.length} projects meet the 50% review threshold.\n`); diff --git a/scripts/public-projects.mjs b/scripts/public-projects.mjs new file mode 100644 index 0000000..079d490 --- /dev/null +++ b/scripts/public-projects.mjs @@ -0,0 +1,30 @@ +// Copyright the Xquik contributors. +// SPDX-License-Identifier: MIT + +export const GITHUB_ORG = "Xquik-dev"; + +export const SHARED_REPOSITORY_NAMES = [".github", "xquik-docs"]; + +export const OPENSSF_PROJECTS = [ + ["hermes-tweet", 13725], + ["n8n-nodes-xquik", 13726], + ["paperclip-plugin-xquik", 13727], + ["prefect-xquik", 13728], + ["terraform-provider-x-twitter-scraper", 13729], + ["tweetclaw", 13730], + ["x-twitter-scraper", 13731], + ["x-twitter-scraper-cli", 13732], + ["x-twitter-scraper-csharp", 13733], + ["x-twitter-scraper-go", 13734], + ["x-twitter-scraper-java", 13735], + ["x-twitter-scraper-kotlin", 13736], + ["x-twitter-scraper-php", 13737], + ["x-twitter-scraper-python", 13738], + ["x-twitter-scraper-ruby", 13739], + ["x-twitter-scraper-typescript", 13740], + ["xquik-haystack", 13741], +]; + +export const OPENSSF_PROJECT_IDS = new Map(OPENSSF_PROJECTS); + +export const OPENSSF_PROJECT_NAMES = OPENSSF_PROJECTS.map(([name]) => name);