Skip to content

feat(ownsharp-cli): release-readiness pipeline for the NuGet package (P-013)#244

Closed
PhysShell wants to merge 2 commits into
mainfrom
claude/ownsharp-cli-release-prep-2026-07-12
Closed

feat(ownsharp-cli): release-readiness pipeline for the NuGet package (P-013)#244
PhysShell wants to merge 2 commits into
mainfrom
claude/ownsharp-cli-release-prep-2026-07-12

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

Phase 3 of a repository-state cleanup: prepares OwnSharp.Cli for eventual NuGet publication without publishing a production package (per the explicit boundary).

Package metadata — added Authors, PackageProjectUrl, RepositoryUrl, RepositoryType, PackageTags, PackageReadmeFile to the csproj. PackageLicenseExpression is deliberately left unset — the repository has no LICENSE file 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 independent dotnet pack runs produce byte-identical payload (CLI dll, bundled extractor dll, every vendored ownlang-core .py file, all satellite assemblies — checked via sha256sum per file). Only NuGet's own per-pack OPC wrapper GUID (.rels/.psmdcp) differs, which is a property of the .nupkg container 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-packsmoke-test (matrix: ubuntu + windows) → publish. The smoke test installs and runs the actual packed .nupkg from outside any checkout (never a project reference or dotnet 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 pushed ownsharp-cli-v* tag (a workflow_dispatch run can never reach it) and a nuget-release GitHub Environment a repo admin still has to configure with required reviewers — it doesn't exist yet. secrets.NUGET_API_KEY is only ever passed as a dotnet nuget push argument, 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).

Тип изменения

  • feat — новая возможность (release infrastructure, packaging-only)

Как проверено

  • python tests/run_tests.py — 276/276
  • ruff check . / mypy — clean
  • Workflow YAML parsed with python3 -c "import yaml; yaml.safe_load(...)"
  • Both actions/upload-artifact@v4 and actions/download-artifact@v4 SHA pins verified against git ls-remote --tags (not guessed from memory)
  • Local end-to-end replication of the entire smoke-test job on this machine: dotnet pack (twice, for determinism) → unzip + inspect contents (CLI dll, extractor dll, 20 vendored core .py files, README all present) → dotnet tool install --global --add-source <local feed>ownsharp --versionownsharp check on a seeded leak (exit 1, OWN001) → ownsharp check on clean code (exit 0) → OWN_PYTHON pointed 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).

Чеклист

  • изменение покрыто тестом/селфтестом — the new release workflow is the test surface; every assertion in it was hand-verified locally first
  • README/docs обновлены — OwnSharp.Cli/README.md links to the new release note; docs/notes/ownsharp-cli-release.md is new
  • коммиты в conventional-commit стиле (feat:)

Boundaries honored

  • No package published to nuget.org.
  • No personal API key requested or stored — the workflow only references secrets.NUGET_API_KEY by name.
  • No public compatibility promise chosen beyond what the repo already supports (0.x alpha).
  • No analyzer or CLI behavior changed — packaging/CI/docs only.
  • No runtime dependency bundled or downloaded beyond what was already documented in OwnSharp.Cli/README.md.

Generated by Claude Code

…(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.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d3e4d62-443f-49f9-91dc-c8d9d1fa8887

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5a86b and d2b2a91.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .github/workflows/ownsharp-cli-release.yml
  • docs/notes/ownsharp-cli-release.md
  • frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj
  • frontend/roslyn/OwnSharp.Cli/README.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/ownsharp-cli-release-prep-2026-07-12

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread .github/workflows/ownsharp-cli-release.yml Outdated
Comment thread .github/workflows/ownsharp-cli-release.yml Outdated
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).

Copy link
Copy Markdown
Owner Author

Both findings confirmed and fixed in d2b2a91:

  1. Publish gate holeif: startsWith(github.ref, 'refs/tags/ownsharp-cli-v') didn't require the trigger to be an actual tag push; workflow_dispatch --ref ownsharp-cli-v0.1.0 would have satisfied it. Added github.event_name == 'push' &&. Applied the same fix to the version-match assertion step and to action-marketplace-readiness.yml's analogous job for consistency.
  2. Source ambiguity in the smoke install--add-source doesn't guarantee resolution from the local artifact (dotnet tool install queries all configured sources in parallel, fastest wins). Replaced with an isolated nuget.config (<clear/> + only the local artifact dir) via --configfile, in both the new workflow's smoke-test job and ci.yml's pre-existing ownsharp-cli-smoke job, which had the identical latent bug.

Thanks for catching both — the first one in particular would have quietly defeated the whole point of the publish gate.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Pausing this PR per direction: a public-facade rebrand (product "Owen", package Owen.Cli, command owen) needs to land first so the NuGet publication pipeline doesn't target the pre-rebrand OwnSharp.Cli/ownsharp identity. See #246.

Once #246 merges, this PR will be rebuilt against it — retargeting the release workflow, package metadata, and smoke tests at Owen.Cli/owen instead of OwnSharp.Cli/ownsharp. Not merging this PR as-is.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown

@coderabbitai review

Copy link
Copy Markdown
Owner Author

Superseded by #247 — rebuilt against the merged Owen public facade (#246) using Owen.Cli/owen/owen-cli-v*/OWEN_PYTHON throughout instead of this PR's pre-rebrand OwnSharp.Cli/ownsharp naming. Closing this one in favor of #247, per the reviewer's instruction not to merge it in its current pre-Owen form.


Generated by Claude Code

@PhysShell PhysShell closed this Jul 12, 2026
PhysShell added a commit that referenced this pull request Jul 12, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants