Closes#226, and answers the question it was actually filed for.
The premise holds
publish deletes a destination it could not write to — docs/defect-audit.mdD4's fix — but only when that destination was positively zero bytes first. destinationIsKnownEmpty is careful that "I could not tell" never authorises a delete, which makes the precondition load-bearing.
Until now it was asserted only against a fake built to match it: OutputPublisherPublishTest writes ByteArray(0) into FakeSafProvider before each case, under a comment stating this is how CreateDocument behaves. If it were false in production, D4's fix would be inert and every existing test would still pass.
Measured against the real dialog on API 34: the document SAF hands back is a document URI and does report a size of exactly zero before anything writes to it. RecordingPublisher reads both at the moment publish sees them, through the ConversionDependencies seam, then lets the real copy proceed so the bytes are checked too.
So D4's fix is live. That is a "no defect found", and it is the result — it could not have been known without this.
There is no cheap half, and now both halves are measured
E7 already recorded that a DocumentsProvider is reachable only through a picker-issued grant. This adds the other constraint, found while trying to avoid the picker: a host Activity in this source set owning its own CreateDocument launcher cannot be started at all —
Intent in process org.libremediaconverter resolved to different process
org.libremediaconverter.test
— because instrumentation runs in the target app's process. So the app's own Save button is the only launcher available to drive, which is also the more faithful thing to drive.
Three things the flow needed, each measured rather than guessed
what
why
Both taps scroll first
On Ready the screen carries a file card, five pickers, then the button. performClick on an off-screen node dispatches where nothing is and throws nothing, while assertIsEnabled passes either way — the first version waited for a Converted that could never come.
The format stays at its default
FixtureDocumentsProvider advertises video/mp4 so the picker's MIME filter has a mutation with a shape. DocumentsUI honours that on the save side too: choosing MP3 makes the destination audio/mpeg and the fixture root is filtered out of the dialog entirely.
The notification dialog is dismissed, not pre-granted
Convert converts from the permission callback whichever way the answer goes, so denying is a real user's path and enough. Granting programmatically did not take — GrantPermissionsActivity appeared anyway and swallowed the tap.
Baseline
FixtureDocumentsProvider gains create/write/delete, which it needs to be a save target at all. The test carries @FailsOnEmulatorApi37 because anything that puts DocumentsUI on screen aborts system_server on that image, as #245 established for the other two. Baseline 5 → 6.
Verification — local API 34 emulator
run 1: tests=70 failures=0 errors=0 skipped=3
run 2: tests=70 failures=0 errors=0 skipped=3
run 3: tests=70 failures=0 errors=0 skipped=3
Mutation — publish opens the stream and writes no bytes:
the destination did not receive what was staged: array lengths differed,
expected.length=58677 actual.length=0
Closes #226, and answers the question it was actually filed for.
## The premise holds
`publish` deletes a destination it could not write to — `docs/defect-audit.md` **D4**'s fix — but only when that destination was **positively zero bytes** first. `destinationIsKnownEmpty` is careful that *"I could not tell"* never authorises a delete, which makes the precondition load-bearing.
Until now it was asserted only against a fake built to match it: `OutputPublisherPublishTest` writes `ByteArray(0)` into `FakeSafProvider` before each case, under a comment stating this is how `CreateDocument` behaves. **If it were false in production, D4's fix would be inert and every existing test would still pass.**
Measured against the real dialog on API 34: the document SAF hands back **is** a document URI and **does** report a size of exactly zero before anything writes to it. `RecordingPublisher` reads both at the moment `publish` sees them, through the `ConversionDependencies` seam, then lets the real copy proceed so the bytes are checked too.
So D4's fix is live. That is a "no defect found", and it is the result — it could not have been known without this.
## There is no cheap half, and now both halves are measured
**E7** already recorded that a `DocumentsProvider` is reachable only through a picker-issued grant. This adds the other constraint, found while trying to avoid the picker: a host Activity in this source set owning its own `CreateDocument` launcher **cannot be started at all** —
```
Intent in process org.libremediaconverter resolved to different process
org.libremediaconverter.test
```
— because instrumentation runs in the target app's process. So the app's own Save button is the only launcher available to drive, which is also the more faithful thing to drive.
## Three things the flow needed, each measured rather than guessed
| what | why |
|---|---|
| **Both taps scroll first** | On `Ready` the screen carries a file card, five pickers, then the button. `performClick` on an off-screen node dispatches where nothing is and throws nothing, while `assertIsEnabled` passes either way — the first version waited for a `Converted` that could never come. |
| **The format stays at its default** | `FixtureDocumentsProvider` advertises `video/mp4` so the picker's MIME filter has a mutation with a shape. DocumentsUI honours that on the **save** side too: choosing MP3 makes the destination `audio/mpeg` and the fixture root is filtered out of the dialog entirely. |
| **The notification dialog is dismissed, not pre-granted** | Convert converts from the permission callback *whichever way the answer goes*, so denying is a real user's path and enough. Granting programmatically did not take — `GrantPermissionsActivity` appeared anyway and swallowed the tap. |
## Baseline
`FixtureDocumentsProvider` gains create/write/delete, which it needs to be a save target at all. The test carries `@FailsOnEmulatorApi37` because anything that puts DocumentsUI on screen aborts `system_server` on that image, as #245 established for the other two. **Baseline 5 → 6.**
## Verification — local API 34 emulator
```
run 1: tests=70 failures=0 errors=0 skipped=3
run 2: tests=70 failures=0 errors=0 skipped=3
run 3: tests=70 failures=0 errors=0 skipped=3
```
Mutation — `publish` opens the stream and writes no bytes:
```
the destination did not receive what was staged: array lengths differed,
expected.length=58677 actual.length=0
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #226, and answers the question it was actually filed for.
The premise holds
publishdeletes a destination it could not write to —docs/defect-audit.mdD4's fix — but only when that destination was positively zero bytes first.destinationIsKnownEmptyis careful that "I could not tell" never authorises a delete, which makes the precondition load-bearing.Until now it was asserted only against a fake built to match it:
OutputPublisherPublishTestwritesByteArray(0)intoFakeSafProviderbefore each case, under a comment stating this is howCreateDocumentbehaves. If it were false in production, D4's fix would be inert and every existing test would still pass.Measured against the real dialog on API 34: the document SAF hands back is a document URI and does report a size of exactly zero before anything writes to it.
RecordingPublisherreads both at the momentpublishsees them, through theConversionDependenciesseam, then lets the real copy proceed so the bytes are checked too.So D4's fix is live. That is a "no defect found", and it is the result — it could not have been known without this.
There is no cheap half, and now both halves are measured
E7 already recorded that a
DocumentsProvideris reachable only through a picker-issued grant. This adds the other constraint, found while trying to avoid the picker: a host Activity in this source set owning its ownCreateDocumentlauncher cannot be started at all —— because instrumentation runs in the target app's process. So the app's own Save button is the only launcher available to drive, which is also the more faithful thing to drive.
Three things the flow needed, each measured rather than guessed
Readythe screen carries a file card, five pickers, then the button.performClickon an off-screen node dispatches where nothing is and throws nothing, whileassertIsEnabledpasses either way — the first version waited for aConvertedthat could never come.FixtureDocumentsProvideradvertisesvideo/mp4so the picker's MIME filter has a mutation with a shape. DocumentsUI honours that on the save side too: choosing MP3 makes the destinationaudio/mpegand the fixture root is filtered out of the dialog entirely.GrantPermissionsActivityappeared anyway and swallowed the tap.Baseline
FixtureDocumentsProvidergains create/write/delete, which it needs to be a save target at all. The test carries@FailsOnEmulatorApi37because anything that puts DocumentsUI on screen abortssystem_serveron that image, as #245 established for the other two. Baseline 5 → 6.Verification — local API 34 emulator
Mutation —
publishopens the stream and writes no bytes:🤖 Generated with Claude Code