-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
275 lines (269 loc) · 13.4 KB
/
Copy pathaction.yml
File metadata and controls
275 lines (269 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
name: "Owen lifetime/resource check"
description: >-
Scan C# for lifetime/resource leaks the compiler cannot express — event/timer
subscription leaks, undisposed IDisposable fields/locals, ignored Subscribe()
tokens, ArrayPool buffers rented-but-never-returned — and annotate the PR.
author: "Own.NET"
branding:
icon: "shield"
color: "purple"
inputs:
path:
description: "File(s) or directory to scan (directories are walked for *.cs)."
required: false
default: "."
config:
description: >-
Explicit Owen configuration file (own.toml). Today it carries the P-035
[weak-subscription].subscribe allowlist of your project's own weak-subscribe
wrapper API ("SimpleType.Method"); a matching call is treated as an already-
released subscription. Left empty, no config is read.
required: false
default: ""
format:
description: >-
Finding surface: github (PR annotations), msbuild, human, or sarif — a SARIF
2.1.0 log for GitHub code scanning, written to a file and exposed as the
sarif-file output (feed it to github/codeql-action/upload-sarif).
required: false
default: "github"
severity:
description: "How findings are shown: error (default) or warning (advisory)."
required: false
default: "error"
engine:
description: >-
Which analysis engine runs. Since #262 Stage 3 the DEFAULT is rust (the
Rust core `own-cli ownir`); python selects the reference implementation
and is the documented ROLLBACK for the observation window; compare runs
both over one captured input and exposes the reference's result only when
they agree byte for byte. A Rust failure is never turned into a Python
success: there is no fallback, and selecting python is something you do
on purpose. compare is a development/CI seam for the migration, not yet a
promised feature.
required: false
default: "rust"
fail-on-finding:
description: >-
Whether a FINDING fails the step. Default false: findings are published
as annotations/SARIF and the step succeeds, so adding Owen to a repository
cannot turn its CI red on day one. Set true once you are ready to gate on
it. This input governs findings ONLY — an operational failure (the
analyser crashed, the input could not be read, no SARIF could be written)
always fails the step, in either mode.
required: false
default: "false"
python-version:
description: "Python version for the Owen core."
required: false
default: "3.13"
dotnet-version:
description: "The .NET SDK version for the Roslyn extractor."
required: false
default: "8.0.x"
sarif-file:
description: >-
Where to write the SARIF log when format: sarif (default:
$RUNNER_TEMP/owen.sarif). The chosen path is echoed back as the
sarif-file output regardless.
required: false
default: ""
outputs:
sarif-file:
description: >-
Path to the SARIF 2.1.0 log — set only when format: sarif. Hand it to
github/codeql-action/upload-sarif to publish to code scanning.
value: ${{ steps.own.outputs.sarif-file }}
runs:
using: "composite"
steps:
# Python is still set up unconditionally, and on purpose: it is the
# documented ROLLBACK (engine: python) for the observation window, and
# `compare` needs both engines. Stage 3 changes which engine answers by
# default, not what the Action ships with. Removing this is Stage 4.
- name: Set up Python (Owen reference engine / rollback)
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Set up .NET (Roslyn extractor)
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.dotnet-version }}
# #262 Stage 3: the Rust core, for the engines that need one.
#
# The Action resolves its candidate by BUILDING the production `own-cli`
# crate from this action's own pinned ref. That is not a developer adapter
# and it is never own-shadow-engine or a stub: it is the same crate, the
# same --release profile and the same binary the release package carries,
# built from exactly the revision the caller pinned, which is also what
# makes the engine and the fixtures provably the same vintage.
#
# It is, however, a real cost to every consumer: a Rust toolchain and a
# first build. It is what is available today -- this repository publishes
# no release, so there is no `own-cli` artifact to download and no
# published Owen.Cli package to install -- and it should be revisited the
# moment a release exists, at which point downloading the released binary
# for the runner's platform is strictly better. Recorded rather than
# smoothed over, because the cost lands on users of this Action.
#
# OWNER RULING (#262 Stage 3): ACTION-BUILD is ACCEPTED for Stage 3 as a
# DECLARED TEMPORARY DISTRIBUTION COST. It is not a parity difference, not a
# semantic difference and not a Stage-3 blocker: the default is rust, Python
# stays the explicit rollback until Stage 4, the Rust toolchain is installed
# by this Action rather than assumed, and what is built is the production
# `own-cli` crate -- never own-shadow-engine and never a test adapter. So it
# is not an undeclared runtime dependency. It is simply a heavy way to
# deliver a binary: a consumer today pays setup-python, setup-dotnet,
# setup-rust and a cargo build to run a static analyzer.
#
# EXIT CONDITION: the first suitable published own-cli / Owen.Cli artifact.
# At that point this Action downloads an immutable platform binary and the
# consumer-side Rust build disappears. That is a separate post-Stage-3
# packaging follow-up and deliberately NOT Stage 4, which is about removing
# the Python distribution dependency -- a different question.
#
# Skipped entirely for `engine: python`, so the rollback path stays as
# cheap as it was before Stage 3.
# THE PINNED QUALIFICATION TOOLCHAIN. This is the canonical place it is
# written down, and `tests/test_stage3_surfaces.py` refuses a floating
# channel here.
#
# `stable` was wrong for a consumer-facing surface, and wrong in a way that
# only shows up later: the action ref is pinned, the dependency graph is
# pinned by rust/Cargo.lock, and the compiler was the one input left
# drifting in time. A caller who pins PhysShell/Own.NET@<tag> is entitled to
# have that tag mean one thing; with a moving channel the same tag would
# build with whatever rustc shipped that month, and a future release that
# compiled the crate differently -- or refused it -- would change the
# behaviour of a revision nobody touched. For a migration cutover that is a
# variable with no upside.
- name: Set up Rust (Owen core)
if: inputs.engine != 'python'
uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
with:
toolchain: "1.98.1"
- name: Cache the built Owen core
if: inputs.engine != 'python'
uses: actions/cache@v4
with:
path: |
${{ github.action_path }}/rust/target
~/.cargo/registry
~/.cargo/git
# The cache is an OPTIMIZATION; the Cargo build below is authoritative.
#
# An earlier comment here claimed this key gives a caller who bumps the
# pinned version a rebuild rather than a stale binary. It does not, and
# the claim was worth more than the key: `github.action_ref` is whatever
# the caller wrote, so a moving major tag like `v1` keeps one key across
# every commit it ever points at. What actually prevents a stale binary
# is Cargo -- it rebuilds a local crate whose sources changed, cache hit
# or not -- so the correctness argument belongs there and not here.
key: owen-core-${{ runner.os }}-${{ github.action_ref || github.sha }}
- name: Build the Owen core (own-cli)
if: inputs.engine != 'python'
shell: bash
run: |
set -euo pipefail
cd "${{ github.action_path }}/rust"
# --locked: rust/Cargo.lock is committed, so there is no reason for a
# production build on a consumer's runner to be allowed to recompute the
# dependency graph. Without it a yanked or newly published version can
# change what gets built from an unchanged source revision, and the
# build would succeed while quietly not being the qualified one.
cargo build -p own-cli --release --locked
ext=""
if [ "$RUNNER_OS" = "Windows" ]; then ext=".exe"; fi
core="$(cd target/release && pwd)/own-cli$ext"
# Prove it runs on THIS runner before anything depends on it, so a
# broken candidate is a clear failure here rather than an obscure one
# inside a check.
"$core" --version
echo "OWEN_RUST_CORE=$core" >> "$GITHUB_ENV"
- name: Owen leak check
id: own
shell: bash
env:
# Pass user-controlled inputs through the environment (data), not by
# template interpolation into the script body (code) — otherwise a path
# like `.; rm -rf x` would be expanded into the shell before bash parses
# it. CodeRabbit #10.
OWN_PATH: ${{ inputs.path }}
OWN_FORMAT: ${{ inputs.format }}
OWN_SEVERITY: ${{ inputs.severity }}
OWN_ENGINE: ${{ inputs.engine }}
OWN_FAIL_ON_FINDING: ${{ inputs.fail-on-finding }}
OWN_SARIF_FILE: ${{ inputs.sarif-file }}
OWN_CONFIG: ${{ inputs.config }}
run: |
# D2: the Action is one of the four launcher surfaces, but its
# engine-selection SEMANTICS are own-check.sh's — it delegates rather
# than re-implementing them, so the real fan-out is smaller than four
# and there is exactly one contract to keep true.
check="${{ github.action_path }}/scripts/own-check.sh"
# P-035: forward an explicit own.toml to own-check when the caller set one.
# Passed as data via OWN_CONFIG (never interpolated into the script body).
config_args=()
if [ -n "$OWN_CONFIG" ]; then
config_args+=(--config "$OWN_CONFIG")
fi
if [ "$OWN_FORMAT" = "sarif" ]; then
# The code-scanning surface: write a SARIF 2.1.0 log to a file and expose
# its path as an output, so the caller hands it to
# github/codeql-action/upload-sarif. Code scanning renders inline PR
# annotations *and* the Security tab — it subsumes the `github` format,
# so this stays a single own-check run, not two. --fail-on-finding is
# passed so the true exit code (0 clean / 1 findings / >=2 hard error) is
# captured *after* the file is written; whether a finding fails the step
# is the action's own fail-on-finding (default: let code scanning gate).
sarif="${OWN_SARIF_FILE:-$RUNNER_TEMP/owen.sarif}"
set +e
"$check" --root "${{ github.action_path }}" --format sarif \
--severity "$OWN_SEVERITY" --engine "$OWN_ENGINE" "${config_args[@]}" \
--fail-on-finding -- "$OWN_PATH" > "$sarif"
rc=$?
set -e
echo "sarif-file=$sarif" >> "$GITHUB_OUTPUT"
echo "Owen wrote SARIF to $sarif ($(wc -c < "$sarif" 2>/dev/null || echo 0) bytes; own-check rc=$rc)"
if [ "$rc" -ge 2 ]; then
echo "::error::Owen hard error (bad facts / drifted contract)"
cat "$sarif" >&2 || true
exit "$rc"
fi
# Below the hard-error tier the log must exist and be non-empty. A
# missing/empty file with rc<2 means the *write* failed, not that there
# were findings — e.g. a custom sarif-file whose parent dir is absent
# fails the redirection (exit 1, outside own-check's 0/1/>=2 contract).
# Surface it; never let it pass as a gated-off finding with no SARIF.
if [ ! -s "$sarif" ]; then
echo "::error::Owen produced no SARIF at $sarif (own-check rc=$rc) — the run or its redirection failed"
exit 1
fi
if [ "$OWN_FAIL_ON_FINDING" = "true" ] && [ "$rc" -eq 1 ]; then
exit 1
fi
exit 0
fi
# Always ask own-check for its TRUE tier (0 clean / 1 findings / >=2
# operational failure) and decide the step's status here. Run WITHOUT
# --fail-on-finding and the script folds findings into 0, making tier 1
# indistinguishable from tier 0 — and tier 1 is the only one this
# action is allowed to negotiate about.
set +e
"$check" --root "${{ github.action_path }}" --format "$OWN_FORMAT" \
--severity "$OWN_SEVERITY" --engine "$OWN_ENGINE" "${config_args[@]}" \
--fail-on-finding -- "$OWN_PATH"
rc=$?
set -e
if [ "$rc" -ge 2 ]; then
# NOT a finding: the analyser crashed, the input could not be read, or
# the contract drifted. "Annotations instead of failure" is a policy
# about defects found in your code, never about the tool failing to
# look — that must not reach anyone as a green check.
echo "::error::Owen could not complete the analysis (exit $rc). This is an operational failure, not a finding — fail-on-finding does not apply to it. The diagnostic is above."
exit "$rc"
fi
if [ "$rc" -eq 1 ] && [ "$OWN_FAIL_ON_FINDING" = "true" ]; then
exit 1
fi
exit 0