Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2a86601
P-022 Stage 3: freeze the cutover decision contract before implementa…
claude Sep 18, 2026
b5d9272
P-022 Stage 3: close the three Python-first cutover hygiene tails
claude Sep 18, 2026
f11ab55
P-022 Stage 3: re-run the #261 CLI mutation campaign on the Stage-3 tree
claude Sep 18, 2026
04c3303
P-022 Stage 3: measure cancellation instead of contracting a number
claude Sep 18, 2026
81157a8
P-022 Stage 3: D6, the Rust core ships in the package
claude Sep 18, 2026
501c70d
P-022 Stage 3: the public default is Rust on all four launcher surfaces
claude Sep 18, 2026
4ba765f
P-022 Stage 3: the rollback, as four states that must stay distinct
claude Sep 18, 2026
b590bf4
P-022 Stage 3: an unset locator is D6, not an invalid locator
claude Sep 18, 2026
d1da48e
P-022 Stage 3: re-qualify the differential evidence on the candidate …
claude Sep 18, 2026
2ecf501
P-022 Stage 3: fix what the first CI run on the cutover actually found
claude Sep 18, 2026
8c8d2e6
P-022 Stage 3: derive the #262 decision packet from evidence, not fro…
claude Sep 18, 2026
4767c3a
P-022 Stage 3: re-anchor the Stage-1/Stage-2 campaigns the cutover in…
claude Sep 18, 2026
f988c8e
P-022 Stage 3: reconcile the status surfaces so they cannot disagree
claude Sep 18, 2026
6ed880d
P-022 Stage 3: re-record the Stage-1 campaign against the inverted mu…
claude Sep 18, 2026
0d6686e
P-022 Stage 3: harden ACTION-BUILD for reproducibility, and record th…
claude Sep 18, 2026
f69f098
P-022 Stage 3: re-record the Stage-2 campaign; both inverted mutants …
claude Sep 18, 2026
baf3771
P-022 Stage 3: fix the own-check.ps1 output regression the cutover in…
claude Sep 18, 2026
f1d3681
P-022 Stage 3: record the Windows campaign, and fix the assertion tha…
claude Sep 18, 2026
7622a3b
P-022 Stage 3: COMPLETE — reconcile at the terminal candidate
claude Sep 18, 2026
b225436
Repository hygiene: untrack rust/target-fault and ignore Cargo build …
claude Sep 18, 2026
1bf17e4
P-022 Stage 3: record the implementation qualification on the cleaned…
claude Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 186 additions & 13 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions .github/workflows/mine-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ jobs:
fi
{ echo "target=$target"; echo "ref=$ref"; echo "paths=$paths"; } >> "$GITHUB_OUTPUT"
echo "mine-on-push: $target (ref='${ref:-HEAD}' paths='${paths:-*}')"
# #262 Stage 3: scripts/mine.sh drives the BARE launcher surface, whose
# default engine is now the Rust core, so this job supplies a candidate.
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
with:
toolchain: stable
- name: Build the production own-cli and select it
run: |
set -euo pipefail
(cd rust && cargo build -p own-cli --release)
"$PWD/rust/target/release/own-cli" --version
echo "OWEN_RUST_CORE=$PWD/rust/target/release/own-cli" >> "$GITHUB_ENV"
- name: Mine the target
env:
REPO: ${{ steps.target.outputs.target }}
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/mine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ jobs:
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: "8.0.x"
# #262 Stage 3: scripts/mine.sh drives the BARE launcher surface, whose
# default engine is now the Rust core, so this job supplies a candidate.
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
with:
toolchain: stable
- name: Build the production own-cli and select it
run: |
set -euo pipefail
(cd rust && cargo build -p own-cli --release)
"$PWD/rust/target/release/own-cli" --version
echo "OWEN_RUST_CORE=$PWD/rust/target/release/own-cli" >> "$GITHUB_ENV"
- name: Mine the target
env:
REPO: ${{ inputs.repo }}
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/oracle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ jobs:
run: |
scan="target"; [[ -n "$PATHS" ]] && scan="target/$PATHS"
set +e
scripts/own-check.sh --format sarif --severity warning -- "$scan" > own.txt 2> own-extract.log
# --engine python is EXPLICIT (#262 Stage 3). This is a class-B call
# site in the Stage-2 census: a cross-tool comparison against Infer#
# and CodeQL, where the reference implementation is the INSTRUMENT.
# When the public default moved to Rust a bare call here would have
# silently changed which engine the comparison is of, so the engine
# this oracle has always measured is now named rather than inherited.
scripts/own-check.sh --engine python --format sarif --severity warning -- "$scan" > own.txt 2> own-extract.log
echo "own-check rc=$? ; own.txt is a SARIF log ($(wc -c < own.txt) bytes)"

# CodeQL — database from source (no build). The dispose/leak queries
Expand Down
131 changes: 128 additions & 3 deletions .github/workflows/owen-cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,68 @@ env:
CLI_PROJECT: frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj

jobs:
# P-022 Stage 3 (#262), D6: the Rust core ships INSIDE the package, so it has
# to be built before the package is made, and built for every platform the
# package supports.
#
# Natively, on each platform's own runner, rather than cross-compiled from
# one. Cross-compiling would be cheaper and would prove less: the binary a
# Windows user runs would then be one no Windows machine had ever executed,
# and #262's whole distribution section exists because "it built" and "it
# works where it lands" are different claims. The platform key each leg
# stages under is the one RustCoreLocator.PlatformKey() computes at runtime,
# so the two sides cannot drift into disagreeing about where the binary is.
build-rust-core:
name: build own-cli (${{ matrix.platform_key }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform_key: linux-x64
binary: own-cli
- os: windows-latest
platform_key: win-x64
binary: own-cli.exe
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
with:
toolchain: stable
- name: cargo build -p own-cli --release
working-directory: rust
run: cargo build -p own-cli --release
# The binary is exercised HERE, on the machine that built it, before it is
# allowed anywhere near a package. A candidate that cannot answer
# --version is not one to ship, and finding that out at pack time beats
# finding it out from a user.
- name: The built candidate runs on its own platform
run: |
set -euo pipefail
"rust/target/release/${{ matrix.binary }}" --version
- name: Stage it under the platform key the locator computes
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/rust-stage/${{ matrix.platform_key }}"
cp "rust/target/release/${{ matrix.binary }}" \
"$RUNNER_TEMP/rust-stage/${{ matrix.platform_key }}/${{ matrix.binary }}"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: own-cli-${{ matrix.platform_key }}
path: ${{ runner.temp }}/rust-stage/${{ matrix.platform_key }}/${{ matrix.binary }}
retention-days: 14
if-no-files-found: error

build-test-pack:
name: build + test + pack
runs-on: ubuntu-latest
needs: build-rust-core
outputs:
version: ${{ steps.version.outputs.version }}
steps:
Expand Down Expand Up @@ -113,8 +172,34 @@ jobs:
fi
echo "OK: tag matches csproj Version ($csproj_version)"

# D6: collect every platform's candidate, then pack with them.
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: own-cli-*
path: ${{ runner.temp }}/rust-stage
- name: Stage the Rust cores for packing
run: |
set -euo pipefail
# download-artifact lands each artifact in a directory named after
# itself (own-cli-linux-x64/own-cli), and the locator looks under the
# PLATFORM KEY (linux-x64/own-cli). Rename rather than reshape the
# artifact names: the platform key is the contract, and the artifact
# name is just how CI moves bytes between jobs.
cd "$RUNNER_TEMP/rust-stage"
for d in own-cli-*; do
mv "$d" "${d#own-cli-}"
done
chmod +x linux-x64/own-cli
ls -laR .
# Both platforms, named. A pack that quietly lost a leg would produce
# a package whose default engine is missing on exactly one OS, and
# the smoke test on the OTHER leg would still be green.
test -f linux-x64/own-cli || { echo "FAIL: no linux-x64 own-cli staged"; exit 1; }
test -f win-x64/own-cli.exe || { echo "FAIL: no win-x64 own-cli.exe staged"; exit 1; }
- name: dotnet pack
run: dotnet pack "$CLI_PROJECT" -c Release -o "$RUNNER_TEMP/nupkg"
run: |
dotnet pack "$CLI_PROJECT" -c Release -o "$RUNNER_TEMP/nupkg" \
-p:OwenRustCoreDir="$RUNNER_TEMP/rust-stage"
- name: Inspect package contents (bundled runtime/core assets present)
run: |
set -euo pipefail
Expand All @@ -133,7 +218,24 @@ jobs:
core_py_count=$(find "$RUNNER_TEMP/nupkg-inspect/tools/net8.0/any/ownlang-core/ownlang" -name "*.py" 2>/dev/null | wc -l)
[ "$core_py_count" -gt 0 ] \
|| { echo "FAIL: vendored ownlang core .py files missing from the package"; exit 1; }
echo "OK: package contains the CLI, the bundled extractor, and $core_py_count vendored core .py files"
# D6 (#262 Stage 3): the engine the package makes DEFAULT must be in
# the package, for every supported platform. Asserted per platform
# and by size: a zero-byte or truncated payload would satisfy a bare
# existence test and fail at the only moment that matters.
rc_root="$RUNNER_TEMP/nupkg-inspect/tools/net8.0/any/rust-core"
for leg in "linux-x64/own-cli" "win-x64/own-cli.exe"; do
test -s "$rc_root/$leg" \
|| { echo "FAIL: packed Rust core missing or empty: rust-core/$leg"; exit 1; }
echo " rust-core/$leg: $(wc -c < "$rc_root/$leg") bytes"
done
# And nothing ELSE is under rust-core/: a stray file there is either a
# platform nobody supports or a payload packed twice, and both have
# happened while this was being built.
extra=$(find "$rc_root" -type f | wc -l)
[ "$extra" -eq 2 ] \
|| { echo "FAIL: rust-core/ holds $extra files, expected exactly 2"; \
find "$rc_root" -type f; exit 1; }
echo "OK: package contains the CLI, the bundled extractor, $core_py_count vendored core .py files, and both platforms' Rust core"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: owen-cli-nupkg
Expand Down Expand Up @@ -247,10 +349,33 @@ jobs:
set -e
echo "$out"
[ "$rc" -eq 0 ] || { echo "FAIL: expected exit 0 on clean code, got $rc"; exit 1; }
# #262 Stage 3: the packed artifact's DEFAULT engine is Rust and carries
# its own binary (D6), so a bare check must work with no usable Python.
- name: "Stage 3: the packed artifact's default engine needs no Python"
run: |
set +e
out=$(OWEN_PYTHON=/definitely/does/not/exist/python3 owen check "$RUNNER_TEMP/sample" --fail-on-finding 2>&1)
rc=$?
set -e
echo "$out"
[ "$rc" -eq 1 ] || { echo "FAIL: bare owen check with an unusable OWEN_PYTHON exited $rc, expected 1 (findings) — the packed default still needs Python"; exit 1; }
echo "$out" | grep -q "OWN001" || { echo "FAIL: expected OWN001 from the default (Rust) engine"; exit 1; }
- name: "Stage 3: --engine python is the tested ROLLBACK, on the same install"
run: |
set +e
out=$(owen check --engine python "$RUNNER_TEMP/sample" --fail-on-finding 2>&1)
rc=$?
set -e
echo "$out"
[ "$rc" -eq 1 ] || { echo "FAIL: the documented rollback exited $rc, expected 1 (findings)"; exit 1; }
echo "$out" | grep -q "OWN001" || { echo "FAIL: the rollback engine produced no finding"; exit 1; }
echo "OK: Python remains selectable and working on the shipped package"
- name: No Python found -> fast actionable failure (never an auto-download)
run: |
set +e
out=$(OWEN_PYTHON=/definitely/does/not/exist/python3 owen check "$RUNNER_TEMP/sample" 2>&1)
# --engine python is EXPLICIT (#262 Stage 3): a bare invocation now
# runs Rust and never consults OWEN_PYTHON.
out=$(OWEN_PYTHON=/definitely/does/not/exist/python3 owen check --engine python "$RUNNER_TEMP/sample" 2>&1)
rc=$?
set -e
echo "$out"
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/shadow-sweep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,22 @@ jobs:
- name: Build the dev-only engine adapter
working-directory: rust
run: cargo build --release -p own-shadow --bin own-shadow-engine
# #262 Stage 3: the extraction below invokes `own-check.sh` BARE, and the
# bare surface now runs Rust, so this leg needs a candidate or it exits 2
# before writing a single fact.
#
# The candidate is supplied rather than the command changed. The
# extraction command is part of this sweep's frozen definition and is
# compared against it (tests/shadow_sweep.py), so adding `--engine python`
# here would have meant re-writing the definition and the recorded runs
# that match it. Supplying a candidate also keeps the extraction running
# the actual PUBLIC default, which for a gate about the public product is
# the better of the two.
- name: Build the production own-cli (the bare surface's default engine)
working-directory: rust
run: cargo build --release -p own-cli
- name: Select it
run: echo "OWEN_RUST_CORE=$PWD/rust/target/release/own-cli" >> "$GITHUB_ENV"
# ONE extraction. `--emit-facts` persists the file stage 1 already wrote;
# stage 2 is the reference's verdict path and is neither a second
# extraction nor the comparison.
Expand Down Expand Up @@ -243,6 +259,13 @@ jobs:
- name: Build the dev-only engine adapter
working-directory: rust
run: cargo build --release -p own-shadow --bin own-shadow-engine
# #262 Stage 3, same reason as the matrix leg above: the bare surface's
# default engine is Rust, so this leg needs a candidate to extract at all.
- name: Build the production own-cli (the bare surface's default engine)
working-directory: rust
run: cargo build --release -p own-cli
- name: Select it
run: echo "OWEN_RUST_CORE=$PWD/rust/target/release/own-cli" >> "$GITHUB_ENV"
- name: Extract the OwnIR facts, exactly once
run: |
scripts/own-check.sh --format sarif --severity warning \
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ node_modules/
# Never commit other projects' code; promote interesting findings into
# corpus/real-world/ as minimal reduced cases instead.
corpus/mined/

# Cargo build directories, ignored explicitly at the root. `rust/.gitignore`
# already ignores `target/`, which is why `rust/target/` never entered the tree
# and why `rust/target-fault/` did: that pattern does not match a sibling with
# a different name. `target-fault` is the build directory of the
# fault-injection candidate (`cargo build -p own-cli --release --features
# fault-injection --target-dir target-fault`, ci.yml); commit 04c3303 tracked
# 223 files of it. Build output is not evidence and is never versioned.
/rust/target/
/rust/target-fault/
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,33 @@ git clone https://github.com/PhysShell/Own.NET && cd Own.NET
scripts/own-check.sh --format human -- /path/to/your/csharp/repo
```

Needs Python 3.11+ and the .NET SDK on `PATH` — nothing to build, nothing to
`pip install`. A packaged single-command CLI (`owen check`, package
`Owen.Cli`) also exists — build-and-install-locally today, not yet published
to nuget.org; see
Needs the .NET SDK on `PATH`, plus **one analysis engine**. Since P-022 Stage 3
(#262) the default engine is the **Rust core**, and this script runs from a
checkout, so it needs a candidate binary — build one and point the ratified
locator at it:

```bash
(cd rust && cargo build -p own-cli --release)
export OWEN_RUST_CORE="$PWD/rust/target/release/own-cli"
```

…or skip the build entirely and run the **Python reference** engine, which needs
Python 3.11+ and is the documented rollback:

```bash
scripts/own-check.sh --engine python --format human -- /path/to/your/csharp/repo
```

There is no discovery and no fallback: an unset or unusable `OWEN_RUST_CORE` is
a visible configuration error (exit 2) that tells you both of the above, never a
silent switch to the other engine. See
[`docs/notes/owen-engine-rollback.md`](docs/notes/owen-engine-rollback.md) for
the full engine-selection contract.

The packaged single-command CLI (`owen check`, package `Owen.Cli`) needs none of
this: it **ships** the Rust core inside the package and resolves it itself, so a
bare `owen check` works with no environment variables and no Python at all. It
is build-and-install-locally today, not yet published to nuget.org; see
[`frontend/roslyn/OwnSharp.Cli/README.md`](frontend/roslyn/OwnSharp.Cli/README.md)
and [`docs/notes/alpha-readiness.md`](docs/notes/alpha-readiness.md) gate **A**.

Expand Down
Loading
Loading