feat(ownsharp-cli): release-readiness pipeline for the NuGet package (P-013)#244
feat(ownsharp-cli): release-readiness pipeline for the NuGet package (P-013)#244PhysShell wants to merge 2 commits into
Conversation
…(P-013) Prepares OwnSharp.Cli for eventual nuget.org publication without publishing a production package. Packaging/CI/docs only — no analyzer or CLI behavior change. - Package metadata: Authors, PackageProjectUrl, RepositoryUrl, RepositoryType, PackageTags, PackageReadmeFile added to the csproj. PackageLicenseExpression deliberately left unset and flagged as a blocker: the repository has no LICENSE file anywhere, and choosing one isn't this change's call to make. - Deterministic/ContinuousIntegrationBuild added and verified locally: two independent `dotnet pack` runs produce byte-identical payload (CLI dll, bundled extractor dll, every vendored ownlang-core .py file) - only NuGet's own per-pack OPC wrapper GUID differs, which is a property of the .nupkg container format, not a determinism gap. - New .github/workflows/ownsharp-cli-release.yml: build+test+pack, package-content inspection (bundled extractor + vendored core files must be present), then a clean-install smoke test on both ubuntu-latest and windows-latest that installs and runs the actual packed .nupkg (never a project reference or `dotnet run`) from outside any checkout - version check, leak sample (exit 1, OWN001), clean sample (exit 0), no-Python actionable failure (exit 3), and a full uninstall/reinstall cycle. Publish is gated on both a pushed `ownsharp-cli-v*` tag (workflow_dispatch can never reach it) and a `nuget-release` GitHub Environment a repo admin must still configure with required reviewers. Every one of these steps was replicated and verified manually on this machine before being written into the workflow. - docs/notes/ownsharp-cli-release.md: the versioning policy (csproj <Version> is the single source of truth; tag must match it exactly), the determinism findings, the metadata audit, and the release checklist (license -> version bump -> package inspection -> install test -> tag -> protected publish -> post-publish smoke test against the real nuget.org feed). No package published. No API key requested or stored - the workflow only references secrets.NUGET_API_KEY by name.
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3f4d831d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Both from independent review of PR #244: - publish's `if: startsWith(github.ref, 'refs/tags/ownsharp-cli-v')` did not require the trigger to actually be a tag push - a `workflow_dispatch` run with `--ref ownsharp-cli-v0.1.0` sets github.ref to that same tag ref, satisfying the check and letting a manual dispatch reach the publish job after smoke/environment approval. This directly contradicted the documented safety invariant ("a workflow_dispatch run can never reach publish"). Fixed by also requiring `github.event_name == 'push'`. Applied the same fix to the version-match assertion step (lower stakes, same gap) and to action-marketplace-readiness.yml's analogous validate-release-tag job for consistency. - `dotnet tool install --global ... --add-source <local>` does not guarantee the install resolves from the local artifact: `dotnet tool install` queries every configured source (the machine's default nuget.org feed included) in parallel and takes whichever answers first. Once a same-numbered version ever actually exists on nuget.org, the smoke test could silently install from there instead of the just-packed artifact - defeating the "install the actual packed .nupkg" critical test rule. Fixed with an isolated nuget.config (`<clear/>` + only the local artifact directory) passed via --configfile, in both the new release workflow's smoke-test job and ci.yml's pre-existing ownsharp-cli-smoke job (same latent bug, same fix).
|
Both findings confirmed and fixed in d2b2a91:
Thanks for catching both — the first one in particular would have quietly defeated the whole point of the publish gate. Generated by Claude Code |
|
Pausing this PR per direction: a public-facade rebrand (product "Owen", package Once #246 merges, this PR will be rebuilt against it — retargeting the release workflow, package metadata, and smoke tests at Generated by Claude Code |
|
@coderabbitai review |
|
Superseded by #247 — rebuilt against the merged Owen public facade (#246) using Generated by Claude Code |
…facade (P-013) * feat(owen-cli): release-readiness pipeline, rebuilt against the Owen facade (P-013) Rebuild of PR #244's release-prep work directly against post-#246 main, using the Owen public identity throughout instead of the pre-rebrand ownsharp/OwnSharp.Cli names PR #244 used (per review: do not merge the old-brand version, rebuild it against the merged facade). Package metadata (OwnSharp.Cli.csproj) — added Authors, PackageProjectUrl, RepositoryUrl, RepositoryType, PackageTags, PackageReadmeFile, and deterministic-pack properties (Deterministic, ContinuousIntegrationBuild) on top of #246's already-Owen-branded PackageId/ToolCommandName/Description -- these are additive metadata, not a rebrand of what #246 already set. PackageLicenseExpression is deliberately left unset: the repository has no LICENSE file, and choosing one is the repository owner's call, not this session's -- flagged as the first blocker in the release checklist, not resolved unilaterally. New release workflow (.github/workflows/owen-cli-release.yml, replacing PR #244's ownsharp-cli-release.yml under the Owen tag namespace owen-cli-v*): build-test-pack -> smoke-test (ubuntu + windows) -> publish. The smoke-test job installs and exercises the already-packed Owen.Cli artifact from build-test-pack's upload -- no checkout, no rebuild, no invocation of the pre-rebrand `ownsharp` command -- verifying `owen --version`, a leak sample (exit 1, OWN001), a clean sample (exit 0), the OWEN_PYTHON-not-found actionable-failure path (exit 3), and a full uninstall/reinstall cycle. Publish still requires both a pushed owen-cli-v* tag (workflow_dispatch can never reach it) and a `nuget-release` GitHub Environment a repo admin has to configure with required reviewers -- it doesn't exist yet. Internal filenames (ownsharp.dll, ownsharp-extract.dll -- the real, unmodified AssemblyName-derived output, per #246's established internal/ public split) are intentionally left as-is in the package-content inspection step; only the public package ID, command name, tag namespace, and env var are Owen-branded. docs/notes/owen-cli-release.md (replacing PR #244's ownsharp-cli-release.md) documents the versioning policy, the verified determinism findings (re-verified against this exact source: two packs, byte-for-byte identical payload, only NuGet's own per-pack OPC wrapper GUID differs), the metadata audit, and the release checklist -- including the license blocker addressed explicitly to the repository owner. Every step in the new workflow was manually replicated and verified locally before being encoded: dotnet pack (twice, for determinism) -> diff unzipped payload -> package-content inspection -> isolated-feed install -> owen --version / leak / clean / OWEN_PYTHON-not-found / uninstall-reinstall, all matching what the workflow asserts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw * owen-cli-release: fail loudly if nuget-release lacks protection rules Codex review (P1): GitHub auto-creates a referenced-but-never-configured environment on first use, with zero protection rules. `environment: nuget-release` alone is therefore not proof a human ever approves this job -- if the environment is never actually set up under Settings -> Environments and NUGET_API_KEY already exists as a repository secret, a tag push could reach `dotnet nuget push` with no approval at all, silently defeating the safety story the workflow documents. The publish job's first step now queries the GitHub API for the environment's protection_rules and refuses to publish -- fails loudly, before the artifact is even downloaded -- when that array is empty, converting "never configured" from a silent bypass into a loud failure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw * owen-cli-release: exercise the workflow on PRs, require actual reviewers Two independent review findings, both closed: 1. owen-cli-release.yml only triggered on tag push / workflow_dispatch, so a green PR check never actually ran build-test-pack -> smoke-test on either OS -- only a real tag push did, and PRs never push tags. Added a pull_request trigger scoped to the paths that matter (the CLI/extractor projects, ownlang, the workflow file itself, the shared protection script). The publish job's existing tag-push condition is unchanged, so it stays skipped on pull_request exactly as it already was on workflow_dispatch. 2. The environment-protection check (`.protection_rules | length`) accepted ANY protection rule -- a wait_timer-only or branch_policy-only environment, or a required_reviewers rule saved with zero reviewers, none of which actually gate on a human. Extracted the real predicate into scripts/check_environment_protection.sh: accepts only a required_reviewers rule with >=1 reviewer. The publish job now checks out just scripts/ (sparse), fetches the environment via `gh api` into a file, and calls the script. Its permissions gained `actions: read`, which the environment-read endpoint requires alongside `contents: read`. Fixture-driven, offline test in ci.yml's new environment-protection-selftest job (runs on every ordinary push/PR, no GitHub API or real Environment needed): zero rules, wait_timer-only, branch_policy-only, and required_reviewers-with-zero-reviewers all reject; required_reviewers with one reviewer accepts. The same script and fixtures will also back action-marketplace-readiness.yml's analogous move-major-tag check (PR #248, stacked on this branch for now -- duplication is temporary until #247 merges and #248 rebases cleanly onto main). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw * owen-cli-release: fix doc drift, close two script-injection nitpicks CodeRabbit review: - docs/notes/owen-cli-release.md quoted the publish gate as just `startsWith(github.ref, 'refs/tags/owen-cli-v')`, dropping the `github.event_name == 'push'` half the workflow's own comment explains is required to block a `workflow_dispatch --ref owen-cli-vX` bypass -- the exact scenario the doc's next sentence claimed was already impossible. Quoted the real condition in full. - `${{ steps.version.outputs.version }}` (grep'd from the csproj, which this workflow now also builds on pull_request) and `${{ needs.build-test-pack.outputs.version }}` were both inlined directly into run: scripts at five sites -- the same expression-into-shell-text pattern zizmor flags elsewhere in this repo. Both now flow through env: (a job-level OWEN_CLI_VERSION for smoke-test's four sites, a step-level CSPROJ_VERSION for the tag/version-match assertion) instead of raw interpolation. Not implemented: CodeRabbit's NuGet Trusted Publishing (OIDC) suggestion for the publish step. Real publication is already blocked on the license and Environment decisions the docs already flag as the repository owner's call, not this session's; swapping the auth mechanism before either of those is resolved is the same category of decision, not a code fix. Re-verified locally end-to-end after the env: refactor: pack, isolated-feed install, `owen --version`, and the full uninstall/reinstall cycle all still pass with the version flowing through the environment instead of inline interpolation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw --------- Co-authored-by: Claude <noreply@anthropic.com>
Что и зачем
Phase 3 of a repository-state cleanup: prepares
OwnSharp.Clifor eventual NuGet publication without publishing a production package (per the explicit boundary).Package metadata — added
Authors,PackageProjectUrl,RepositoryUrl,RepositoryType,PackageTags,PackageReadmeFileto the csproj.PackageLicenseExpressionis deliberately left unset — the repository has noLICENSEfile anywhere (checked root + README), and picking one isn't this PR's call. Flagged as the first blocker in the release checklist.Deterministic pack — added
Deterministic/ContinuousIntegrationBuild, then verified locally: two independentdotnet packruns produce byte-identical payload (CLI dll, bundled extractor dll, every vendoredownlang-core.pyfile, all satellite assemblies — checked viasha256sumper file). Only NuGet's own per-pack OPC wrapper GUID (.rels/.psmdcp) differs, which is a property of the.nupkgcontainer format itself, not a determinism gap — documented precisely so this isn't mistaken for a bug later.New release workflow (
.github/workflows/ownsharp-cli-release.yml):build-test-pack→smoke-test(matrix: ubuntu + windows) →publish. The smoke test installs and runs the actual packed.nupkgfrom outside any checkout (never a project reference ordotnet run— the explicit critical test rule) and verifies version, a leak sample (exit 1,OWN001), a clean sample (exit 0), the no-Python actionable-failure path (exit 3), and a full uninstall→reinstall cycle. Publish requires both a pushedownsharp-cli-v*tag (aworkflow_dispatchrun can never reach it) and anuget-releaseGitHub Environment a repo admin still has to configure with required reviewers — it doesn't exist yet.secrets.NUGET_API_KEYis only ever passed as adotnet nuget pushargument, never echoed.Every step in the smoke test was manually replicated and verified on this machine before being written into the workflow (see "Как проверено").
docs/notes/ownsharp-cli-release.md— the versioning policy (csproj<Version>is the single source of truth; a release tag must match it byte-for-byte or the workflow fails loudly), the determinism findings, the metadata audit, and the release checklist (license → version bump → package inspection → install test → tag → protected publish → post-publish smoke test against the real nuget.org feed).Тип изменения
Как проверено
python tests/run_tests.py— 276/276ruff check ./mypy— cleanpython3 -c "import yaml; yaml.safe_load(...)"actions/upload-artifact@v4andactions/download-artifact@v4SHA pins verified againstgit ls-remote --tags(not guessed from memory)dotnet pack(twice, for determinism) → unzip + inspect contents (CLI dll, extractor dll, 20 vendored core.pyfiles, README all present) →dotnet tool install --global --add-source <local feed>→ownsharp --version→ownsharp checkon a seeded leak (exit 1,OWN001) →ownsharp checkon clean code (exit 0) →OWN_PYTHONpointed at a nonexistent binary (exit 3, actionable message) →dotnet tool uninstall→ reinstall → version check again. All matched what the workflow asserts.Связанные issue
Refs #202, P-013. No issues closed (release infra, not a shipped release).
Чеклист
OwnSharp.Cli/README.mdlinks to the new release note;docs/notes/ownsharp-cli-release.mdis newfeat:)Boundaries honored
secrets.NUGET_API_KEYby name.0.xalpha).OwnSharp.Cli/README.md.Generated by Claude Code