ReviewOS

also looking at this

craft-native/craft

test(android): eleven refusals recorded, so the ratchet cannot be gamed

#153
Merged glennmichael123 wants to merge test/android-rejection-deferrals into main
1 file +44 -4
packages/zig/test/android_conformance_test.zigmodified+44-4
Changes to packages/zig/test/android_conformance_test.zig
@@ -141,9 +141,8 @@ const Deferral = struct {
141141/// the tenth, and the two guards below are the whole point: a row Zig starts
142142/// serving fails, and so does a row naming an action the Kotlin no longer has.
143143///
144/// Deliberately non-exhaustive. Ninety-one actions are unmigrated and almost
145/// all of them are simply not reached yet, which is an honest state that needs
146/// no row. A reason is owed only where someone would otherwise try and find
144/// Deliberately non-exhaustive. Most unmigrated actions are simply not reached
145/// yet, which is an honest state that needs no row. A reason is owed only where someone would otherwise try and find
147146/// out the hard way — and demanding one for the rest would invite an invented
148147/// reason, which is worse than silence.
149148const deliberate_deferrals = [_]Deferral{
@@ -172,6 +171,47 @@ const deliberate_deferrals = [_]Deferral{
172171 // and that is a change to the shim rather than a migration away from it.
173172 .{ .action = "setFlashlight", .reason = "writes isFlashlightOn, which toggleFlashlight reads; Zig cannot reach the field" },
174173 .{ .action = "toggleFlashlight", .reason = "reads isFlashlightOn, which only the Kotlin setFlashlight maintains" },
174
175 // ---- Actions whose whole implementation is a refusal --------------
176 //
177 // These eight reject with a message and do nothing else. They are the
178 // easiest actions in the file to "migrate" — the body is one string — and
179 // migrating one would be a way of making this ratchet lie.
180 //
181 // The number counts actions Zig *serves*, meaning actions a page gets an
182 // answer from Zig for. Moving a refusal across changes which language
183 // holds the string and nothing a page can observe, so it would lower the
184 // count without serving anything. That is worth recording explicitly,
185 // because the incentive runs the other way: eleven rows here are eleven
186 // easy decrements left on the table.
187 //
188 // Each is also honest as it stands, which is the other half of the reason.
189 // Unlike `setBadge`, a page calling these is told plainly that the feature
190 // is not there.
191 .{ .action = "purchase", .reason = "the Kotlin rejects with 'Purchase flow not fully implemented'; porting a refusal serves nothing" },
192 .{ .action = "scanQRCode", .reason = "the Kotlin rejects; ML Kit scanning was never wired up" },
193 .{ .action = "signInWithGoogle", .reason = "the Kotlin rejects; Google Sign In needs build.gradle configuration" },
194 .{ .action = "startAR", .reason = "the Kotlin rejects; ARCore needs Activity integration that does not exist" },
195 .{ .action = "placeARObject", .reason = "the Kotlin rejects, for the same missing ARCore integration" },
196 .{ .action = "removeARObject", .reason = "the Kotlin rejects, for the same missing ARCore integration" },
197 .{ .action = "sendToWatch", .reason = "the Kotlin rejects; Wear OS needs a companion app" },
198 .{ .action = "updateWatchContext", .reason = "the Kotlin rejects, for the same missing companion app" },
199
200 // ---- And three that answer successfully instead ------------------
201 //
202 // Same shape, opposite honesty. `startAR` refuses to begin a session, and
203 // then these two report success for operations on the session it refused
204 // to create: `stopAR` resolves `{stopped: true}` and `getARPlanes`
205 // resolves `[]`.
206 //
207 // The empty array is the one that costs something. A page cannot tell it
208 // from "AR is running and has found no planes yet", so the honest failure
209 // `startAR` already gave is undone by the next call. Recorded rather than
210 // migrated for the same reason as `setBadge`: there is no working
211 // behaviour to carry across, and porting the fabrication would spread it.
212 .{ .action = "stopAR", .reason = "resolves {stopped:true} for a session startAR always refuses to open" },
213 .{ .action = "getARPlanes", .reason = "resolves [], which a page cannot tell from AR running with no planes found" },
214 .{ .action = "closePDF", .reason = "resolves true and does nothing; the Kotlin says PDFs open in an external viewer" },
175215};
176216
177217test "every recorded deferral is real, and still a deferral" {
@@ -205,7 +245,7 @@ test "every recorded deferral is real, and still a deferral" {
205245 }
206246
207247 // Non-vacuity: the loop above is satisfied by an empty table.
208 try testing.expect(deliberate_deferrals.len >= 4);
248 try testing.expect(deliberate_deferrals.len >= 15);
209249
210250 // And the table cannot claim more than remain unmigrated.
211251 try testing.expect(deliberate_deferrals.len <= max_not_yet_migrated);