Skip to content

feat(owen): public facade rebrand for the CLI, Action, and SARIF identity#246

Merged
PhysShell merged 5 commits into
mainfrom
claude/owen-public-facade-rebrand-2026-07-12
Jul 12, 2026
Merged

feat(owen): public facade rebrand for the CLI, Action, and SARIF identity#246
PhysShell merged 5 commits into
mainfrom
claude/owen-public-facade-rebrand-2026-07-12

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Public-facade rebrand — not an internal refactor. Per direction: pause the NuGet publication work (PR #244) and introduce this as a separate PR, to land before Phase 3/Phase 4 so neither publishes the first real package under a C#-locked identity (OwnSharp.Cli/ownsharp) that doesn't match how the project actually wants to present itself (language-neutral product "Owen", currently shipping a .NET/C# frontend, with a TypeScript frontend possible later).

Public identity implemented:

  • NuGet package ID: OwnSharp.CliOwen.Cli (confirmed unclaimed on nuget.org via the v3 flat-container API at time of writing — worth a final re-check immediately before an actual publish)
  • Tool command: ownsharpowen
  • Action display name → Owen lifetime/resource check; default SARIF filename → owen.sarif
  • SARIF tool.driver.nameOwen (the one shared point in ownlang/ownir.py + ownlang/diag_sarif.py)
  • Cache directory → ~/.owen/core/<version>/, with a simple fallback read (not a migration subsystem) from ~/.ownsharp/core/<version>/ if that exact version is already unpacked there
  • Env var: OWEN_PYTHON preferred; legacy OWN_PYTHON still works as a temporary fallback and prints a deprecation note when it's the one actually used

Kept unchanged (internal names — not mass-renamed): the OwnSharp.Cli project/namespace, OwnSharp.Extractor (project + its real ownsharp-extract.dll output filename), ownlang (package/module/CLI), OwnIR, every OWN0xx code, the Rust crates, every frontend/* directory name, historical docs/issues, the GitHub repo name, and scripts/own-check.sh (the Action still calls it internally). AssemblyName in the csproj stays ownsharp — it names the internal dll the owen shim launches, never seen by a user.

CLI contract additions:

  • owen --help is language-neutral at the product level, while explicitly listing Included frontend: .NET / C# (.cs, .csproj, .sln) — no speculative TypeScript claim, no plugin framework.
  • Unsupported input now fails explicitly (new exit code 4, explicit message) instead of silently printing "0 findings" — previously indistinguishable from a genuinely clean scan. CheckCommand.HasSupportedInput checks this before the extractor runs.

Full account (including the deliberate non-changes and the reasoning) is in docs/notes/owen-public-facade.md.

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

  • feat — новая возможность (public-facing rebrand)

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

  • python tests/run_tests.py — 276/276 (two pre-existing SARIF-driver-name pins in tests/test_diag_sarif.py/tests/test_ownir.py updated from "Own.NET" to "Owen" — caught by the suite failing before the fix, not scope creep)
  • ruff check . / mypy — clean
  • All changed YAML parsed with python3 -c "import yaml; yaml.safe_load(...)"
  • Full local end-to-end replication of every new CI assertion before writing them into ci.yml: build → pack Owen.Cli → install via an isolated nuget.configowen --help/--version/unknown-command → leak sample (exit 1, OWN001) → clean sample (exit 0) → SARIF driver name is Owen → unsupported input (exit 4, explicit message, confirmed the literal clean-scan phrase 0 findings. does not appear) → OWEN_PYTHON not-found path (exit 3) → legacy OWN_PYTHON fallback (resolves + prints deprecation note) → fresh-install cache goes to ~/.owen/, pre-existing ~/.ownsharp/ cache is reused in place

Связанные issue

Refs #202, P-013. Supersedes the branding assumptions in PR #244 and PR #245's first drafts — both will be rebuilt against this PR once merged.

Чеклист

  • изменение покрыто тестом/селфтестом — ownsharp-cli-smoke (job key kept, content rewritten) pins every piece of the new contract on both OSes; every assertion was hand-verified locally first
  • README/docs обновлены — root README.md/README.ru.md, frontend/roslyn/OwnSharp.Cli/README.md, docs/notes/alpha-readiness.md (active install/usage instructions only — no historical note rewritten), new docs/notes/owen-public-facade.md
  • коммиты в conventional-commit стиле (feat:)

PR separation

  1. This PR — public facade rebrand, no publish.
  2. Phase 3 (NuGet release pipeline) — to be rebuilt against this PR once merged, retargeting Owen.Cli/owen.
  3. Phase 4 (Marketplace preparation) — to be rebuilt against this PR once merged, retargeting the Owen lifetime/resource check display name.

No oracle remeasurement or analyzer-semantics change is mixed into this PR — the extractor and every diagnostic verdict are byte-for-byte unchanged.


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced the Owen CLI facade: owen check via the Owen.Cli package.
    • Added explicit exit-code handling for unsupported or non-analyzable inputs (exit code 4).
    • Implemented content-verified, fingerprinted caching with legacy fallback and quarantine/rebuild on tampering/mismatches.
    • Updated Python selection precedence to OWEN_PYTHON, with deprecated OWN_PYTHON fallback.
    • Updated SARIF branding/driver identity and default output to owen.sarif.
  • Documentation

    • Refreshed setup instructions, action naming, and alpha-readiness notes to consistently use Owen branding (including English and Russian).
  • Tests / CI

    • Strengthened smoke checks and SARIF validations to match Owen identity and new unsupported-input behavior.

…tity

Public product identity is "Owen" going forward - a package published
as "OwnSharp.Cli" with the permanent command "ownsharp" would have
locked the public identity to a single-language framing the project
doesn't actually have (Own.NET is language-neutral at the OwnIR/core
level and may ship a TypeScript frontend later). This is a public
facade rebrand, not an internal refactor - see
docs/notes/owen-public-facade.md for the full account of what changed
and, just as deliberately, what did not.

Public identity implemented:
- NuGet package ID: OwnSharp.Cli -> Owen.Cli (confirmed unclaimed on
  nuget.org at time of writing via the v3 flat-container API)
- Tool command: ownsharp -> owen
- Action display name: "Own.NET resource-leak check" -> "Owen
  lifetime/resource check"; default SARIF filename own-net.sarif ->
  owen.sarif
- SARIF tool.driver.name: "Own.NET" -> "Owen" (ownlang/ownir.py +
  ownlang/diag_sarif.py - the one place both the C#-facts and the
  .own-diagnostics SARIF paths share)
- Cache directory: ~/.ownsharp/core/<version>/ -> ~/.owen/core/<version>/,
  with a simple fallback read (not a migration subsystem) from the
  previous location if a matching version was already unpacked there
- Env var: OWEN_PYTHON preferred; legacy OWN_PYTHON still works as a
  temporary fallback and prints a deprecation note when it's the one
  actually used

Kept unchanged (internal names, not mass-renamed): the OwnSharp.Cli
project/namespace, OwnSharp.Extractor (project + its real
ownsharp-extract.dll output filename), ownlang (package/module names,
CLI, PYTHONPATH conventions), OwnIR, every OWN0xx diagnostic code, the
Rust crates, every frontend/* directory name, historical docs/issues,
the GitHub repo name, and scripts/own-check.sh (the Action still calls
it internally). AssemblyName in the csproj stays "ownsharp" - it names
the internal dll the owen shim launches, never seen by a user, so
renaming it adds no user value.

CLI contract additions:
- `owen --help` is language-neutral at the product level while
  explicitly listing the one frontend actually wired in today
  ("Included frontend: .NET / C# (.cs, .csproj, .sln)") - no
  speculative TypeScript claim, no plugin framework added.
- Unsupported input (e.g. a .ts file, or a directory with no .cs
  files) now fails explicitly with a new exit code 4 and an explicit
  message, instead of silently printing "0 findings" - indistinguishable
  from a genuinely clean C# scan before this change.
  CheckCommand.HasSupportedInput checks this before the extractor runs.

Tests: ci.yml's ownsharp-cli-smoke job (key kept; only content changed)
now pins package ID, command name, --help framing, --version, the
unknown-command "owen:" prefix, a leak sample and a clean negative
control both run from an installed checkout-free location, the Owen
SARIF driver name, the exit-4 unsupported-input path (asserting the
literal clean-scan phrase "0 findings." does NOT appear), OWEN_PYTHON's
not-found path, and the legacy OWN_PYTHON fallback + its deprecation
note - on both ubuntu-latest and windows-latest. Every one of these
assertions was manually replicated and verified on this machine before
being written into the workflow. Two other pre-existing SARIF
driver-name assertions (ci.yml's own-check-codescan structural
validation, tests/test_diag_sarif.py, tests/test_ownir.py) were updated
to match "Owen" - caught by python tests/run_tests.py failing before
the fix, not scope creep.

PR separation: this is PR 1 of 3. Phase 3 (NuGet release pipeline, PR
#244) and Phase 4 (Marketplace preparation, PR #245) both need to be
rebuilt against this PR once merged, targeting Owen.Cli/owen and the
Owen display name instead of the pre-rebrand identity their first
drafts used. No oracle remeasurement or analyzer-semantics change is
mixed into this PR.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d5cd10e8-9ff0-4ac3-88c3-7b8272a05ad6

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2a758 and 5f8e9ef.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • docs/notes/owen-public-facade.md
  • frontend/roslyn/OwnSharp.Cli/CoreVendor.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/roslyn/OwnSharp.Cli/CoreVendor.cs
  • docs/notes/owen-public-facade.md
  • .github/workflows/ci.yml

📝 Walkthrough

Walkthrough

The PR renames the public CLI and SARIF identity to Owen, updates packaging and documentation, adds explicit unsupported-input exit handling, introduces content-addressed core caching with legacy fallback, changes Python environment precedence, and expands CI smoke coverage.

Changes

Owen public facade

Layer / File(s) Summary
Published identity and documentation
frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj, frontend/roslyn/OwnSharp.Cli/README.md, README.md, README.ru.md, action.yml, docs/notes/*
Public package, command, action, cache-path, and documentation references use Owen.Cli, owen, and Owen-facing naming.
CLI contracts and runtime resolution
frontend/roslyn/OwnSharp.Cli/{Program.cs,CheckCommand.cs,PythonResolver.cs}, frontend/roslyn/OwnSharp.Extractor/Program.cs
CLI help and errors use Owen wording; unsupported inputs return exit code 4; OWEN_PYTHON takes precedence over deprecated OWN_PYTHON.
Content-addressed core cache
frontend/roslyn/OwnSharp.Cli/CoreVendor.cs
Vendored Python content is fingerprinted, verified, atomically published, and matched against the legacy cache before reuse.
SARIF identity alignment
ownlang/{diag_sarif.py,ownir.py}, tests/{test_diag_sarif.py,test_ownir.py}, .github/workflows/ci.yml
SARIF output, tests, and CI validation use Owen as the tool driver name.
Packaging and smoke validation
.github/workflows/ci.yml
CI installs the locally packed Owen.Cli and validates CLI behavior, Python selection, cache integrity, findings, SARIF, and unsupported input trees.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant OwenCli
  participant Extractor
  participant CoreVendor
  participant PythonResolver
  User->>OwenCli: owen check input
  OwenCli->>Extractor: expand input paths
  Extractor-->>OwenCli: sources or exit code 4
  OwenCli->>CoreVendor: prepare vendored core
  CoreVendor-->>OwenCli: verified cache path
  OwenCli->>PythonResolver: resolve Python interpreter
  PythonResolver-->>OwenCli: interpreter or error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: a public-facing rebrand of the CLI, Action, and SARIF identity.
Description check ✅ Passed The description follows the required template and fills the key sections with clear change, type, verification, related issues, and checklist details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/owen-public-facade-rebrand-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.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@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: 377a7b1fb7

ℹ️ 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 on lines +41 to +43
if (File.Exists(legacyMarker))
{
return legacyCacheRoot;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not reuse legacy cache without validating contents

When a machine already has ~/.ownsharp/core/0.1.0/.unpacked from the previous OwnSharp.Cli tool, this branch returns that old cache before unpacking the bundled Owen core; the package <Version> is still 0.1.0, so a same-version marker does not prove the cached Python files contain this commit's changes. In that scenario owen check --format sarif can still run the stale core that emits the old Own.NET SARIF driver (and miss any bundled core fixes), making the rebrand depend on user cache state. Please validate the legacy cache identity/content before reusing it, or force an unpack into the new .owen cache for this release.

Useful? React with 👍 / 👎.

{
if (Directory.Exists(p))
{
if (Directory.EnumerateFiles(p, "*.cs", SearchOption.AllDirectories).Any())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mirror the extractor's tolerant directory walk

For directory inputs this preflight uses SearchOption.AllDirectories, while the extractor's Expand walk uses EnumerationOptions { IgnoreInaccessible = true } specifically to tolerate unreadable subdirectories. If a user scans a tree with a locked/protected child and enumeration reaches it before finding a .cs file, owen now throws before the extractor can run, regressing directory scans that the underlying frontend already handles. Please use the same EnumerationOptions here.

Useful? React with 👍 / 👎.

- CoreVendor's legacy ~/.ownsharp fallback trusted a bare .unpacked
  marker's existence keyed only by version. The CLI's own <Version>
  doesn't change every time the vendored core's content does - this
  rebrand's own SARIF-driver-name change is the concrete proof: same
  0.1.0, different core content - so a machine with a pre-rebrand
  OwnSharp.Cli install's ~/.ownsharp/core/0.1.0/ cache could silently
  serve stale content (the old "Own.NET" driver name) to the new
  Owen.Cli 0.1.0 install. Reproduced locally: seeded a legacy cache
  with the pre-rebrand driver name and confirmed `owen check --format
  sarif` served it unchanged before this fix.

  Fixed by keying the marker on a SHA-256 fingerprint of the bundled
  ownlang/*.py files' names+content instead of bare existence - same
  location, both the new ~/.owen and legacy ~/.ownsharp fallback
  checks now only trust a cache whose marker matches the content this
  install actually bundles. Still the "simple fallback read, not a
  migration subsystem" shape requested - content-aware instead of
  version-only. Verified both directions locally: a stale-content
  legacy cache is now correctly rejected (fresh unpack to ~/.owen),
  and a legitimate matching-content legacy cache is still reused in
  place without an unnecessary ~/.owen copy.

- CheckCommand.HasSupportedInput's directory walk used a plain
  SearchOption.AllDirectories, which throws on a locked/permission-
  denied subdirectory, while the extractor's own Expand() walk
  (OwnSharp.Extractor/Program.cs) uses EnumerationOptions {
  IgnoreInaccessible = true } specifically to tolerate that and keep
  scanning. The new preflight check could have crashed owen check on
  a directory tree the extractor itself would have handled fine.
  Fixed by using the identical EnumerationOptions the extractor
  already relies on.

docs/notes/owen-public-facade.md updated to record both corrections
in place, without erasing the original description of the intended
(simpler, buggier) fallback shape.

Copy link
Copy Markdown
Owner Author

Both confirmed and fixed in 3585bf8:

  1. Stale legacy cache — you're right that a bare .unpacked marker keyed only on version doesn't prove content identity, and this rebrand is the concrete proof (same 0.1.0, different core content). I reproduced it locally: seeded ~/.ownsharp/core/0.1.0/ with the pre-rebrand core (old "Own.NET" driver name) and confirmed owen check --format sarif served it unchanged before the fix. Now CoreVendor keys the marker on a SHA-256 fingerprint of the bundled ownlang/*.py files instead of bare existence — both the new ~/.owen location and the legacy ~/.ownsharp fallback only get trusted when their marker matches the content this install actually bundles. Verified both directions: the stale cache is now correctly rejected (fresh unpack), and a legitimate matching-content legacy cache is still reused in place.

  2. Directory walk on inaccessible subdirectories — confirmed against OwnSharp.Extractor/Program.cs's Expand(), which does use EnumerationOptions { IgnoreInaccessible = true } for exactly this reason. HasSupportedInput now uses the identical options.

Thanks — #1 in particular is a real correctness gap in the rebrand's own headline feature (the SARIF driver name), not a hypothetical.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown

@coderabbitai review

…sensitive extensions

Addresses review round 3 on PR #246 (three findings, all with committed
CI regression tests, not just local verification):

1. Stale-cache pollution on a fingerprint mismatch: the round-2 fix
   overwrote the destination file-by-file and rewrote the marker to match
   the new source, but never removed a file the new source no longer had,
   so a removed-upstream module survived as an orphan while the marker
   claimed the (polluted) result was valid. CoreVendor now uses a
   content-addressed layout (~/.owen/core/<version>/<fingerprint>/) with
   no separate marker -- a fingerprint mismatch is a different path, never
   an in-place overwrite -- and publishes atomically (build into a temp
   sibling, verify the written destination's own fingerprint matches the
   source, then Directory.Move once). The legacy fallback now fingerprints
   the actual legacy destination content and only trusts it on an exact
   match, rejecting extra/missing/modified files instead of patching them.

2. HasSupportedInput's cheap directory check disagreed with the
   extractor's real expansion rules (bin/obj/generated/vendor skips), so a
   directory containing only skipped files passed the CLI preflight but
   produced zero real extractor inputs -- the exact silent-clean-scan bug
   exit 4 exists to prevent. Per review, the CLI no longer tries to
   duplicate the extractor's skip-list; the extractor itself now checks
   Expand(...).Distinct() and returns exit 4 with an explicit message when
   it's empty, and the CLI/own-check.sh's existing exit-code propagation
   carries that through unchanged.

3. SupportedExtensions now compares with StringComparer.OrdinalIgnoreCase
   so Foo.CS/App.CSPROJ are recognized like their lowercase spellings.

ci.yml gains committed regression coverage (both OSes) for all of the
above: matching/extra-file/modified-content legacy cache reuse and
rejection, skipped-only and generated-only directories, an empty .csproj,
a .sln with no projects, skipped-files-plus-real-source, an inaccessible
subtree alongside a readable source, and an uppercase extension.

docs/notes/owen-public-facade.md updated to describe the round-3 design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/roslyn/OwnSharp.Cli/CoreVendor.cs (1)

157-157: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Avoid unnecessary ToArray() allocation in fingerprint computation.

sha.ComputeHash(buffer.ToArray()) copies the entire MemoryStream contents into a new byte array. The SHA256.ComputeHash(Stream) overload can read directly from the MemoryStream after rewinding, avoiding the allocation.

♻️ Proposed refactor
         writer.Flush();
-        return Convert.ToHexString(sha.ComputeHash(buffer.ToArray()));
+        buffer.Position = 0;
+        return Convert.ToHexString(sha.ComputeHash(buffer));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/roslyn/OwnSharp.Cli/CoreVendor.cs` at line 157, Update the
fingerprint computation in the surrounding method to rewind the MemoryStream
before hashing, then use SHA256.ComputeHash(Stream) directly instead of
converting the buffer with ToArray(). Preserve the existing hexadecimal return
value.
docs/notes/owen-public-facade.md (1)

82-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

The embedded CLI help excerpt uses a fenced code block without a language tag, which markdownlint flags (MD040). Add text to the opening fence.

📝 Proposed fix
  • Included frontend:
      .NET / C# (.cs, .csproj, .sln)
    
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/notes/owen-public-facade.md around lines 82 - 85, Update the fenced
code block containing “Included frontend” in the Owen public facade
documentation to use the text language tag on its opening fence, leaving the
embedded CLI help content unchanged.


</details>

<!-- cr-comment:v1:2ce05d99f463fe7e0e058817 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:

  • Around line 2248-2271: Update the workflow step “Inaccessible subtree plus one
    readable source -- tolerated, real source still found” to run only on Linux,
    enforce that locking the directory succeeds instead of ignoring chmod failures,
    and validate the command exits with status 1. Keep the output assertions
    specific: require OWN001 from Real.cs and assert Secret.cs is absent, while
    preserving the existing crash-check.
  • Around line 2079-2084: Replace python3 with python in the SARIF smoke-test
    commands throughout the workflow, including the JSON validation command and the
    corresponding commands near the other referenced steps. Preserve the existing
    scripts and assertions while ensuring they invoke the interpreter exposed by
    actions/setup-python on Windows.

In @frontend/roslyn/OwnSharp.Cli/CoreVendor.cs:

  • Around line 112-120: Update the publication flow around CoreVendor’s
    Directory.Move so an IOException caused by another process creating finalOwnlang
    after the Directory.Exists check is treated as a lost race: return finalRoot
    after confirming the destination now exists, while preserving propagation of
    unrelated move failures and existing cleanup behavior.

Nitpick comments:
In @docs/notes/owen-public-facade.md:

  • Around line 82-85: Update the fenced code block containing “Included frontend”
    in the Owen public facade documentation to use the text language tag on its
    opening fence, leaving the embedded CLI help content unchanged.

In @frontend/roslyn/OwnSharp.Cli/CoreVendor.cs:

  • Line 157: Update the fingerprint computation in the surrounding method to
    rewind the MemoryStream before hashing, then use SHA256.ComputeHash(Stream)
    directly instead of converting the buffer with ToArray(). Preserve the existing
    hexadecimal return value.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `6936e18b-8255-41fa-8d55-0621e6b9862a`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4c5a86b176b04c42f7c1cc9e51aef545a3586cc9 and 9e2a758bac0a584dd91cabf2d45ae9e988855e8a.

</details>

<details>
<summary>📒 Files selected for processing (18)</summary>

* `.github/workflows/ci.yml`
* `README.md`
* `README.ru.md`
* `action.yml`
* `docs/notes/alpha-readiness.md`
* `docs/notes/owen-public-facade.md`
* `frontend/roslyn/OwnSharp.Cli/CheckCommand.cs`
* `frontend/roslyn/OwnSharp.Cli/CoreVendor.cs`
* `frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj`
* `frontend/roslyn/OwnSharp.Cli/Program.cs`
* `frontend/roslyn/OwnSharp.Cli/PythonResolver.cs`
* `frontend/roslyn/OwnSharp.Cli/README.md`
* `frontend/roslyn/OwnSharp.Cli/ToolVersion.cs`
* `frontend/roslyn/OwnSharp.Extractor/Program.cs`
* `ownlang/diag_sarif.py`
* `ownlang/ownir.py`
* `tests/test_diag_sarif.py`
* `tests/test_ownir.py`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread frontend/roslyn/OwnSharp.Cli/CoreVendor.cs Outdated
- ci.yml: use `python` instead of `python3` in the CLI smoke job's SARIF
  driver-name assertions -- actions/setup-python does not expose a python3
  alias on windows-latest, so these would have broken the Windows leg.
- ci.yml: the inaccessible-subtree test is now Linux-only (chmod 000 does
  not reliably lock out the job's own account on Windows) and actually
  proves the lockout worked -- asserts exit 1 and that the output names
  Real.cs but never Secret.cs, instead of a bare "OWN001 somewhere" check
  that would pass just as well if the locked file got analyzed too.
- CoreVendor.cs: close the narrow TOCTOU window between the
  Directory.Exists(finalOwnlang) race check and Directory.Move -- a
  concurrent `owen` process publishing the same fingerprint in between
  now resolves the same way the earlier check already did (their content
  is provably identical), instead of surfacing Move's IOException.
- CoreVendor.cs: Fingerprint hashes the MemoryStream directly (rewound)
  instead of copying it via ToArray() first.
- owen-public-facade.md: language tag on the --help excerpt's code fence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw

Copy link
Copy Markdown
Owner Author

All three findings fixed, with the two required reproductions now committed as ci.yml assertions rather than remaining local-only verification. A follow-up CodeRabbit pass on the fix commit found one real cross-platform bug (python3 doesn't exist on windows-latest's actions/setup-python) plus two robustness/nitpick items; those are folded into f555f6c as well.

New head SHA: f555f6c75009df5d977e6a8e3bfb430a6cb43a03

Cache publication strategy: Content-addressed layout
  ~/.owen/core/<version>/<fingerprint>/ownlang/ (marker file removed
  entirely -- a fingerprint mismatch is now a different path, never an
  in-place overwrite). Publication is atomic: build into a
  .tmp-<guid> sibling, verify the WRITTEN destination's own fingerprint
  matches the source, then Directory.Move once into the final
  fingerprint-named path. A reader only ever sees nothing or a fully
  self-verified copy. (Follow-up fix: the narrow TOCTOU window between
  the pre-move Directory.Exists race-check and the Move itself is now
  closed -- a concurrent `owen` process publishing the same fingerprint
  in between resolves the same way the earlier check does, instead of
  surfacing Move's IOException.)

Destination fingerprint verification: Both the fresh-publish path and
  the legacy ~/.ownsharp/core/<version>/ fallback fingerprint what is
  ACTUALLY on disk at the destination and compare to the source
  fingerprint -- extra, missing, or modified files all fail the check
  and fall through to a fresh unpack. Fingerprint encoding is
  length-prefixed (BinaryWriter, 8-byte length + bytes per field) so
  concatenation can't collide. (Follow-up fix: the hash is now computed
  directly off the rewound MemoryStream via SHA256.ComputeHash(Stream)
  instead of a ToArray() copy.)

Extra-file deletion test: ci.yml -- "Seed a legacy cache with an EXTRA
  (removed) file and confirm it is REJECTED" -- exact reproduction from
  the review (old cache has current.py + removed_module.py, new source
  has only current.py); asserts a fresh ~/.owen unpack happens and
  removed_module.py does not leak into it. Passing on both OSes.

Same-version different-content test: ci.yml -- "Seed a legacy cache
  with MODIFIED content (same filenames, different bytes) and confirm
  rejection" -- tampers ownir.py's bytes, asserts fresh unpack. Also a
  positive-case sibling, "Seed a same-content legacy cache and confirm
  it is reused (no ~/.owen copy)", asserting the legitimate byte-for-
  byte match is reused in place without copying.

Extractor zero-expanded-input behavior: OwnSharp.Extractor/Program.cs
  checks Expand(rawInputs).Distinct() immediately after computing it;
  an empty result writes an explicit diagnostic to stderr and returns
  exit 4. CheckCommand.HasSupportedInput went back to doing ONLY the
  cheap check (existing file with a recognized extension, or an
  existing directory) -- it no longer tries to predict the extractor's
  bin/obj/generated/vendor skip rules. CheckCommand's existing
  extractRc != 0 early-return propagates the extractor's 4 unchanged,
  and own-check.sh's existing `set -e` does the same for the
  script/Action path -- no CLI or script code needed beyond the
  extractor's own check.

Skipped/generated-only directory tests: ci.yml, all asserting exit 4 +
  an explicit "no supported input" message: directory containing only
  bin/obj files; directory containing only *.g.cs; an empty .csproj
  (no Compile items resolve to any .cs); a .sln with no Project( lines.
  Plus two positive controls: skipped files alongside one real source
  (exit 1, OWN001 still found -- skip rules don't over-reject) and an
  inaccessible subtree alongside one readable source (Linux-only per
  the follow-up review -- chmod 000 doesn't reliably lock out the job's
  own account on Windows -- asserting exit 1, Real.cs present in the
  output, and Secret.cs absent, so the test actually proves the lockout
  held rather than passing vacuously if both files got analyzed).

Uppercase-extension test: ci.yml -- SupportedExtensions now uses
  StringComparer.OrdinalIgnoreCase; "Uppercase extensions are accepted
  case-insensitively (Foo.CS)" pins Leak.CS -> exit 1, OWN001 found.

Ubuntu/Windows CI links:
  https://github.com/PhysShell/Own.NET/actions/runs/29195389683/job/86657321404 (ubuntu-latest, success)
  https://github.com/PhysShell/Own.NET/actions/runs/29195389683/job/86657321397 (windows-latest, success)

Also fixed in f555f6c (from the automated follow-up review on the first fix commit):

  • ci.yml's SARIF-driver-name smoke steps called python3, which actions/setup-python does not expose on windows-latest -- switched to python (all 4 call sites).
  • docs/notes/owen-public-facade.md's --help excerpt fence now has a text language tag (markdownlint MD040).

python tests/run_tests.py, ruff check ., and a local dotnet build + pack/install/exercise cycle (all eleven scenarios above, plus the pre-existing smoke matrix) were re-verified before each push, in addition to the CI runs linked above.


Generated by Claude Code

…existence

Round-4 review finding: a hit at the content-addressed
~/.owen/core/<version>/<fingerprint>/ownlang path was trusted on
Directory.Exists alone, and the two concurrent-publisher race checks
around the atomic move made the same assumption. A path name is only a
claim, not proof -- a destination already sitting at the "right"
fingerprint could be modified, corrupted, or hand-assembled after
publication and would then be served as valid.

Every current-path hit now recomputes the fingerprint over what is
actually on disk and only trusts an exact match, the same standard the
legacy-cache fallback already held itself to. A mismatch quarantines the
invalid destination (atomic rename to a .invalid-<guid> sibling, then
best-effort recursive delete -- no in-place delete a concurrent reader
could observe mid-way) and falls through to the same temp-directory +
atomic-move rebuild a fresh unpack uses.

Added the round-4 regression to ci.yml: create the current cache, tamper
ownir.py and add a stale_module.py directly under the fingerprint-named
path, and assert the second run rejects it, rebuilds clean, and the
stale file is gone from whatever cache directory actually got used.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSXTDuh1ZHdQc4QqmYwshw
@PhysShell PhysShell merged commit d61d453 into main Jul 12, 2026
38 checks passed
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.

3 participants