Skip to content

Commit b05b38a

Browse files
authored
Merge pull request #342 from PhysShell/claude/p022-verdict-dual-engine-egg2kq
feat(p022): #260 acceptance over the committed corpus — byte-attested same input, the verdict layer in scope, the derived SARIF gate, the compare driver
2 parents 21fb0c3 + d685b1e commit b05b38a

106 files changed

Lines changed: 9151 additions & 1068 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,92 @@ jobs:
7272
- name: cargo test
7373
run: cargo test
7474

75+
# P-022 step 7a (#260) — COMPARE MODE over the committed corpus. Not #260's
76+
# acceptance: the five-repository sweep and the large-solution controls are
77+
# separate work, and nothing here may be read as shadow mode having been
78+
# achieved.
79+
#
80+
# What it gates: every committed facts document through BOTH engines on
81+
# byte-attested same input, failing on any acceptance-`unexplained`
82+
# observation at any of the three layers, any `renderer-only divergence` on
83+
# the derived SARIF, or any execution failure. The generated compact and
84+
# malformed controls run in the same pass, as the driver's own negative
85+
# controls, so a gate that reported agreement over an empty set would be red.
86+
shadow-compare:
87+
name: shadow compare (committed corpus)
88+
runs-on: ubuntu-latest
89+
steps:
90+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
91+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
92+
with:
93+
python-version: "3.13"
94+
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
95+
with:
96+
toolchain: stable
97+
- name: Build the dev-only engine adapter
98+
working-directory: rust
99+
run: cargo build --release -p own-shadow --bin own-shadow-engine
100+
- name: Compare mode over the committed corpus
101+
env:
102+
OWN_SHADOW_ENGINE: ${{ github.workspace }}/rust/target/release/own-shadow-engine
103+
run: python scripts/shadow_compare.py --engine compare --corpus --quiet --out "$RUNNER_TEMP/shadow"
104+
# The driver's own controls, REQUIRED here rather than skipped: this is
105+
# the job that has the adapter, so it is the job that cannot be allowed
106+
# to pass without exercising it. The raw-variant, negative and
107+
# execution-failure controls all run in this pass.
108+
- name: The compare driver's controls (adapter required)
109+
env:
110+
OWN_SHADOW_ENGINE: ${{ github.workspace }}/rust/target/release/own-shadow-engine
111+
OWN_SHADOW_COMPARE_REQUIRED: "1"
112+
run: python tests/test_shadow_compare.py
113+
- name: Upload the divergence reports
114+
if: failure()
115+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
116+
with:
117+
name: shadow-compare-reports
118+
path: ${{ runner.temp }}/shadow
119+
retention-days: 14
120+
if-no-files-found: ignore
121+
122+
# The same gate over the OwnIR the Roslyn extractor already produced. It
123+
# consumes the artifact `wpf-extractor` published rather than running the
124+
# extractor again — #260 forbids a second extraction, and this is the
125+
# packaging that makes the first one reachable. The driver reads the
126+
# downloaded file ONCE, as bytes.
127+
shadow-compare-samples:
128+
name: shadow compare (C# samples)
129+
runs-on: ubuntu-latest
130+
needs: wpf-extractor
131+
steps:
132+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
133+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
134+
with:
135+
python-version: "3.13"
136+
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
137+
with:
138+
toolchain: stable
139+
- name: Build the dev-only engine adapter
140+
working-directory: rust
141+
run: cargo build --release -p own-shadow --bin own-shadow-engine
142+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
143+
with:
144+
name: csharp-samples-ownir
145+
path: ${{ runner.temp }}/samples
146+
- name: Compare mode over the extracted C# sample facts
147+
env:
148+
OWN_SHADOW_ENGINE: ${{ github.workspace }}/rust/target/release/own-shadow-engine
149+
run: |
150+
python scripts/shadow_compare.py --engine compare \
151+
"$RUNNER_TEMP/samples/facts.json" --quiet --out "$RUNNER_TEMP/shadow"
152+
- name: Upload the divergence report
153+
if: failure()
154+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
155+
with:
156+
name: shadow-compare-samples-report
157+
path: ${{ runner.temp }}/shadow
158+
retention-days: 14
159+
if-no-files-found: ignore
160+
75161
# Own.NET Audit (audit/) — the aggregation layer's selftests, the only thing the
76162
# Linux CI gates for the audit (the target itself is analyzed on a local Windows
77163
# machine, never in CI; see audit/README.md and Plan.md §3.2). PyYAML is scoped
@@ -266,6 +352,19 @@ jobs:
266352
frontend/roslyn/samples/OwnedCollectionElementSample.cs \
267353
-o "$RUNNER_TEMP/facts.json"
268354
cat "$RUNNER_TEMP/facts.json"
355+
# The extractor runs ONCE in this workflow, and #260 forbids running it
356+
# twice: frontend nondeterminism would contaminate any comparison taken
357+
# over its output. Publishing the bytes it already wrote is how the
358+
# shadow compare gate reaches them without a second extraction — the
359+
# file crosses the job boundary unchanged, and the driver's own
360+
# attestation (`input.raw` against each engine's `consumed`) is what
361+
# proves it did.
362+
- name: publish the extracted OwnIR for the shadow compare gate (#260)
363+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
364+
with:
365+
name: csharp-samples-ownir
366+
path: ${{ runner.temp }}/facts.json
367+
retention-days: 7
269368
- name: Extractor source columns (Own.NET#317)
270369
# The producer half of #317. Runs the real extractor over a fixture that puts two
271370
# anchor sites on ONE line and asserts each record's exact (line, column) - the

‎.gitignore‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ __pycache__/
77
# .NET build output (golden_arraypool demo)
88
bin/
99
obj/
10+
# ...but a Rust crate's BINARY TARGETS live in `src/bin/`, and that is source.
11+
# The unanchored `bin/` above swallowed own-shadow's adapter silently: the
12+
# Cargo manifest naming the `[[bin]]` was committed and the file it named was
13+
# not, so the tree built only where the file already existed. Re-included
14+
# explicitly rather than by narrowing `bin/`, which is doing a real job for
15+
# every .NET output directory in the tree.
16+
!rust/crates/*/src/bin/
1017

1118
# Node deps for the OwnTS OSS runtime-confirmation project (frontend/ownts/oss-verify)
1219
node_modules/

‎docs/evidence/p022-coord-1.result.json‎

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"campaign": "p022-coord-1",
55
"definition": "docs/evidence/p022-coord-1.json",
66
"definition_sha256": "5f2587e9f495e5bfe5d702615889e346c733e4b5db3c8a17c734eea99ede3c1f",
7-
"source_commit": "3af8ead18d423d9de760ecbf180ed5ca0ef68dad",
7+
"source_commit": "4c1c9d81972171c3d9c8c1d814debb73a4e471f5",
88
"dirty": false,
9-
"recorded_at": "2026-09-07T10:31:54Z",
9+
"recorded_at": "2026-09-07T14:42:58Z",
1010
"layers": [
1111
"py-limits",
1212
"py-ledger",
@@ -17,7 +17,7 @@
1717
"id": "M00",
1818
"outcome": "survived",
1919
"catchers": [],
20-
"elapsed_seconds": 0.6
20+
"elapsed_seconds": 1.0
2121
},
2222
"mutations": [
2323
{
@@ -27,7 +27,7 @@
2727
"py-ledger::ledger-stale",
2828
"py-limits::line-domain"
2929
],
30-
"elapsed_seconds": 0.6
30+
"elapsed_seconds": 0.8
3131
},
3232
{
3333
"id": "M02",
@@ -36,7 +36,7 @@
3636
"py-ledger::ledger-stale",
3737
"py-limits::line-domain"
3838
],
39-
"elapsed_seconds": 1.0
39+
"elapsed_seconds": 1.4
4040
},
4141
{
4242
"id": "M03",
@@ -48,7 +48,7 @@
4848
"py-limits::nesting",
4949
"py-limits::tolerances"
5050
],
51-
"elapsed_seconds": 1.0
51+
"elapsed_seconds": 1.4
5252
},
5353
{
5454
"id": "M04",
@@ -57,7 +57,7 @@
5757
"py-ledger::ledger-stale",
5858
"py-limits::line-domain"
5959
],
60-
"elapsed_seconds": 1.0
60+
"elapsed_seconds": 1.4
6161
},
6262
{
6363
"id": "M05",
@@ -66,7 +66,7 @@
6666
"py-ledger::ledger-stale",
6767
"py-limits::line-domain"
6868
],
69-
"elapsed_seconds": 1.0
69+
"elapsed_seconds": 1.3
7070
},
7171
{
7272
"id": "M06",
@@ -75,7 +75,7 @@
7575
"py-ledger::ledger-stale",
7676
"py-limits::line-domain"
7777
],
78-
"elapsed_seconds": 1.0
78+
"elapsed_seconds": 1.4
7979
},
8080
{
8181
"id": "M07",
@@ -84,7 +84,7 @@
8484
"py-ledger::ledger-stale",
8585
"py-limits::line-domain"
8686
],
87-
"elapsed_seconds": 1.0
87+
"elapsed_seconds": 1.3
8888
},
8989
{
9090
"id": "M08",
@@ -95,7 +95,7 @@
9595
"py-limits::line-type",
9696
"py-limits::nesting"
9797
],
98-
"elapsed_seconds": 1.0
98+
"elapsed_seconds": 1.3
9999
},
100100
{
101101
"id": "M09",
@@ -104,7 +104,7 @@
104104
"py-ledger::ledger-stale",
105105
"py-limits::line-type"
106106
],
107-
"elapsed_seconds": 1.1
107+
"elapsed_seconds": 1.2
108108
},
109109
{
110110
"id": "M10",
@@ -113,7 +113,7 @@
113113
"py-ledger::ledger-stale",
114114
"py-limits::line-domain"
115115
],
116-
"elapsed_seconds": 1.0
116+
"elapsed_seconds": 1.3
117117
},
118118
{
119119
"id": "M11",
@@ -122,7 +122,7 @@
122122
"py-ledger::ledger-stale",
123123
"py-limits::column-domain"
124124
],
125-
"elapsed_seconds": 1.0
125+
"elapsed_seconds": 1.2
126126
},
127127
{
128128
"id": "M12",
@@ -131,7 +131,7 @@
131131
"py-ledger::ledger-stale",
132132
"py-limits::column-domain"
133133
],
134-
"elapsed_seconds": 1.0
134+
"elapsed_seconds": 1.3
135135
},
136136
{
137137
"id": "M13",
@@ -140,7 +140,7 @@
140140
"py-ledger::ledger-stale",
141141
"py-limits::line-domain"
142142
],
143-
"elapsed_seconds": 1.1
143+
"elapsed_seconds": 1.2
144144
},
145145
{
146146
"id": "M14",
@@ -150,7 +150,7 @@
150150
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
151151
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
152152
],
153-
"elapsed_seconds": 1.3
153+
"elapsed_seconds": 1.5
154154
},
155155
{
156156
"id": "M15",
@@ -160,7 +160,7 @@
160160
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
161161
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
162162
],
163-
"elapsed_seconds": 1.3
163+
"elapsed_seconds": 1.7
164164
},
165165
{
166166
"id": "M16",
@@ -174,7 +174,7 @@
174174
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
175175
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
176176
],
177-
"elapsed_seconds": 1.4
177+
"elapsed_seconds": 1.8
178178
},
179179
{
180180
"id": "M17",
@@ -184,7 +184,7 @@
184184
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
185185
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
186186
],
187-
"elapsed_seconds": 1.4
187+
"elapsed_seconds": 1.8
188188
},
189189
{
190190
"id": "M18",
@@ -194,7 +194,7 @@
194194
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
195195
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
196196
],
197-
"elapsed_seconds": 1.5
197+
"elapsed_seconds": 1.7
198198
},
199199
{
200200
"id": "M19",
@@ -203,15 +203,15 @@
203203
"rust-door/src/lib.rs::protocol::tests::an_absent_line_is_zero_and_an_out_of_domain_one_follows_the_door",
204204
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on"
205205
],
206-
"elapsed_seconds": 1.3
206+
"elapsed_seconds": 1.6
207207
},
208208
{
209209
"id": "M20",
210210
"outcome": "caught",
211211
"catchers": [
212212
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
213213
],
214-
"elapsed_seconds": 1.2
214+
"elapsed_seconds": 1.6
215215
},
216216
{
217217
"id": "M21",
@@ -220,23 +220,23 @@
220220
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
221221
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
222222
],
223-
"elapsed_seconds": 1.2
223+
"elapsed_seconds": 1.8
224224
},
225225
{
226226
"id": "M22",
227227
"outcome": "caught",
228228
"catchers": [
229229
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
230230
],
231-
"elapsed_seconds": 1.2
231+
"elapsed_seconds": 1.7
232232
},
233233
{
234234
"id": "M23",
235235
"outcome": "caught",
236236
"catchers": [
237237
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
238238
],
239-
"elapsed_seconds": 1.3
239+
"elapsed_seconds": 1.7
240240
},
241241
{
242242
"id": "M24",
@@ -245,7 +245,7 @@
245245
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
246246
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
247247
],
248-
"elapsed_seconds": 1.2
248+
"elapsed_seconds": 1.7
249249
},
250250
{
251251
"id": "M25",
@@ -254,7 +254,7 @@
254254
"rust-door/tests/validation_replay.rs::the_coordinate_rules_report_the_axis_they_are_on",
255255
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
256256
],
257-
"elapsed_seconds": 1.2
257+
"elapsed_seconds": 1.7
258258
},
259259
{
260260
"id": "M26",
@@ -263,7 +263,7 @@
263263
"rust-door/src/lib.rs::protocol::tests::an_absent_line_is_zero_and_an_out_of_domain_one_follows_the_door",
264264
"rust-door/tests/validation_replay.rs::the_two_loaders_accept_the_same_language"
265265
],
266-
"elapsed_seconds": 1.4
266+
"elapsed_seconds": 1.9
267267
}
268268
]
269269
}

0 commit comments

Comments
 (0)