Summary
Adds native-broker feasibility spikes for iOS and Android. Both move
liboliphaunt/PostgreSQL into a separate app-owned worker process, keep SQL bytes
off the control plane, classify post-dispatch loss as outcomeUnknown without
SQL replay, and admit recovery only after a fresh process epoch and healthy
query.
This remains a draft feasibility PR. The evidence supports VIABLE WITH
LIMITATIONS, not production readiness or a cross-device reliability SLA.
iOS 26
- Adds
OliphauntBrokerProtocol, OliphauntBrokerXPC,
OliphauntIOSBroker, and OliphauntBrokerExtension Swift products and the
rendered release-package graph.
- Uses
AppExtensionProcess and XPC for control plus a framed AF_UNIX FD data
plane with explicit ownership, bounded input/raw collection, streaming,
cancellation, checkpoint/background controls, diagnostics, and crash
recovery.
- Keeps the native framework, runtime resources, template PGDATA, and selected
extensions in the worker bundle; the host does not link liboliphaunt.
- Adds PostgreSQL patch 0021 for fail-closed configured-LOGIN authentication
and hard stream-queue enforcement by splitting oversized backend output.
Android
- Adds an unexported bound service in
android:process=":broker", a minimal
AIDL/Bundle control plane, and
ParcelFileDescriptor.createReliableSocketPair() data plane.
- Uses a single database executor, independent Binder-thread cancellation, an
independent fail-stop scheduler, Binder death recipients, UUID worker
epochs, and lazy fresh-generation reconnect.
- Adds Android backend-thread cancellation: the Binder/JNI caller signals the
embedded PostgreSQL pthread with SIGINT, preserving PostgreSQL's normal
statement-cancel handler and SQLSTATE 57014 behavior.
- Adds DEBUG experiments for executor deadlock, native output followed by
pg_sleep, post-commit/pre-completion ambiguity, persistent reopen, and
controlled 8/32 MiB socket backpressure.
Shared contract
- The host and database worker use different PIDs and failure domains.
- Control and diagnostics use XPC on iOS and Binder/AIDL on Android.
- PostgreSQL protocol bytes use owned socket FDs, not control messages.
- Every frame/request is generation-scoped; a PID alone is not identity.
- Once request bytes may have reached a worker, worker/transport loss is
outcomeUnknown. The host never replays SQL automatically.
- A replacement is accepted only after a different process epoch, validated
Ready/handshake, and healthy SQL.
- Committed database state remains in one canonical persistent root and is
reopened after worker loss.
iOS retained qualification
The canonical August 10 reports exactly qualified their then-current source.
Later cleanup removed unsupported private diagnostic SPI and mechanically
formatted the staged source, so these are retained feasibility evidence rather
than exact-current-commit qualification.
| Environment |
Retained result |
Recovery result |
| iPhone 17 Pro simulator, iOS 26.4 |
Four lanes; 55/55 checks: 33 semantic, 7 handshake-negative, 10 extended-fault, 5 hang |
Seven injected crash recoveries; immediate deliberate-hang recreation did not obtain a healthy fresh worker |
| iPhone15,2 physical device, iOS 26.5 |
Two signed Debug semantic launches ×ばつ33 plus two signed Release lifecycle launches ×ばつ30; 4 launches / 126 checks |
Four Debug crash recoveries; both background/resume runs returned with a fresh PID and epoch after the old worker was absent |
| Same physical device, signed-Debug hang lane |
1 launch / 5 evidence checks |
Evidence validation passed, but recoveryProven=false: attempts 1→2, Ready 1→1, no recovered PID/epoch |
Canonical physical total including the negative hang lane is 5 launches / 131
checks. PASS for that hang lane means its negative-evidence contract passed;
it is not recovery evidence.
iOS deliberate-hang findings
The first immediate-recreate test did not obtain a fresh healthy worker:
- iOS 26.4 Simulator invalidated and reacquired the same PID, then the new
Hello timed out.
- The iOS 26.5 device made one replacement attempt but produced no second Ready
generation; retained evidence could not distinguish stale-PID reuse from
teardown still being in flight.
A stronger physical DEBUG matrix armed and acknowledged the fault, proved
same-epoch control responsiveness, then deadlocked an ordinary request:
| Public delay before recovery demand |
Result |
| 0 ms |
stale active-channel state; no fresh worker |
| 50 ms (52 ms observed) |
stale active-channel state; no fresh worker |
| 100 ms (106 ms observed) |
fresh PID, epoch, Ready, and healthy SQL |
| 250 ms (265 ms observed) |
fresh PID, epoch, Ready, and healthy SQL |
Unsupported private terminate/unique-instance controls also recovered at zero
delay, but all private ExtensionFoundation SPI was removed. Initial private
terminate prototypes crashed because the experiment used the wrong Swift
calling convention; that SIGSEGV was an experiment bug, not an iOS result.
Apple exposes cancel/invalidate but no public force-fresh flag, PID/exit waiter,
or termination-completion barrier. XPC cancellation is asynchronous and
non-preemptive, and AppExtensionProcess may bind an existing process. The
public delay result supports asynchronous teardown/process reuse as the
immediate mechanism; it does not establish a documented 100 ms threshold.
hangRestartable=false remains the advertised capability.
iOS repeated fail-stop controls
On one physical iPhone 14 Pro / iOS 26.5 beta stack:
- Actor-block control: 60/60. An independent global-queue
_exit(70) fired
while the worker actor remained in a non-returning semaphore wait. Every
trial returned outcomeUnknown at 1.003–1.068 s, then obtained a fresh PID,
epoch, validated Ready generation, and healthy SQL.
- Native SQL control: 60/60. An independent work item called
abort()
during SELECT pg_sleep(60), .... Every trial returned outcomeUnknown,
then obtained a fresh PID/epoch/Ready generation and healthy SQL.
- Three retained crash reports directly show
SIGABRT on the fault-injector
global queue while other threads are simultaneously inside
oliphaunt_exec_protocol_stream and PostgreSQL pg_sleep. Crash-log
coalescing makes this direct corroboration for sampled trials, not all 60.
Under an IID assumption, 60/60 gives a one-sided 95% lower success bound of
95.13%. IID is weak for serial trials on one device/build, so this is
repeatability evidence for the DEBUG mechanism, not a shipping SLA or a
production progress-sensitive watchdog policy.
iOS memory and performance
- Physical Release control RTT medians: 2.264 ms and 2.424 ms, 20
samples each.
- Throttled 8 MiB and 32 MiB streams: 8,399,927 bytes in about 19.95 s and
33,599,543 bytes in about 79.9 s, around 420 KB/s. This proves bounded
backpressure under the imposed reader rate, not maximum throughput.
- Slow-reader physical peaks were about 19.8 MB and 19.4 MB; minimum available
memory was about 16.9 MB and 17.3 MB. Both runs preserved the required 8 MiB
margin and the native 8 MiB queue ceiling.
- Recursive device protection evidence: 1,759/1,759 Class-C entries, zero
missing/mismatched/unavailable/unreadable/symlink entries.
CoreSimulator does not prove extension-private containment, file-protection
metadata, or available-memory headroom. Physical Class-C evidence covers
after-first-unlock, not locked-before-first-unlock behavior.
Android retained qualification
The final API 34 arm64 emulator behavior series is:
target/android-native-broker-spike/runs/pr-final-01-20260811T121603Z/
through
target/android-native-broker-spike/runs/pr-final-10-20260811T121854Z/
pr-doc-sync-v1 is an additional PASS whose source manifest includes the final
README. The ten repeated runs used the same executable inputs; their only
source-file difference is the README evidence-path text. The final committed
spike files match the retained file manifests, but the APK was built from a
dirty, hash-manifested worktree rather than a clean checkout of commit
4db5de94.
All ten full matrices passed the same 11 checks. They used ten unique host
PIDs and produced 40 unique worker PIDs and 40 unique UUID epochs.
| Fault lane |
Samples |
Terminal |
Binder death |
Fresh PID + epoch + healthy SQL |
Request → healthy replacement |
| Executor deadlock + independent SIGABRT |
10/10 |
outcomeUnknown |
10/10 |
10/10 |
2,594–5,240 ms; median 2,762 ms |
Native output then pg_sleep + independent SIGABRT |
10/10 |
outcomeUnknown |
10/10 |
10/10 |
2,698–7,359 ms; median 2,739 ms |
Commit before completed |
10/10 |
outcomeUnknown |
10/10 |
10/10 |
597–823 ms; median 626.5 ms |
All 30 faulted worker PIDs have exact retained Fatal signal 6 records.
Binder-death events are scoped to the exact PID and epoch; first loss signal
varied safely between EOF, binderDied, and onServiceDisconnected.
Android native-path witness and no replay
The native fault executes one ordered query whose first Append child emits 513
8 KiB rows and whose second child runs pg_sleep(60). The worker:
- observes more than 4 MiB in the native PostgreSQL callback;
- completes the threshold-crossing synchronous socket write;
- publishes a generation/request-scoped witness; and
- arms a two-second independent SIGABRT watchdog.
All ten native lanes witnessed exactly 4,202,496 backend bytes, then
observed Binder death and fresh recovery. This strongly proves ordered native
PostgreSQL execution immediately before the sleeping plan child. It is not a
callback from inside pg_sleep; that narrower claim remains unproven. The
report field nativeDispatchObserved is currently an alias for witness
presence and is not used as independent evidence.
Every ambiguous post-commit loss had ambiguousExecutionCount=1 and derived
replayCount=0. The committed marker survived all three broker deaths in every
run. This proves no replay for the instrumented counter and persistent reopen
within each matrix; it is not a generic exactly-once protocol or reboot/power
loss qualification.
Android controlled slow-reader evidence
The host holds an explicit zero-read gate while Binder diagnostics sample a
blocking FD with POLLOUT=false, the same synchronous response write in
progress for at least 300 ms, and unchanged completion counters. It then
releases the gate and drains the complete response from the same generation.
| Stream |
Samples |
Response / chunks |
Full-drain time |
Sampled blocked write |
Accepted-wire upper bound |
| 8 MiB |
10/10 |
8,399,927 B / 1,026 |
432–453 ms; median 437.5 ms |
300–312 ms |
493,920 B every run |
| 32 MiB |
10/10 |
33,599,543 B / 4,102 |
639–766 ms; median 648 ms |
300–314 ms |
493,920 B every run |
The cross-size bound delta was zero while response size quadrupled. This is
direct workload-specific evidence of synchronous socket backpressure, not an
effective SO_SNDBUF measurement or a general process-memory bound.
Memory rose substantially because the experimental host accumulates and
reports the final response:
- 8 MiB PSS span: 30,992,384–31,045,632 B; RSS span:
33,157,120–33,964,032 B.
- 32 MiB PSS span: 97,970,176–101,406,720 B; RSS span:
98,533,376–101,945,344 B.
Gross emulator drain rates include the deliberate gate and diagnostics and are
not maximum SQL/transport throughput or an SLA.
Android negative evidence that changed the harness
final-witness-001: PostgreSQL buffered a small first-statement
CommandComplete until after the sleeping statement, so it could not serve
as a pre-hang witness.
final-output-witness-006: the first transient non-writable socket write
advanced while the client still had not read. The final probe therefore
keeps a read gate closed and resets its candidate until the same sampled
write remains unchanged for at least 300 ms.
Explicit limitations
By design / experimental scope
- Both spikes expose one canonical root/session and do not implement a
production multi-client/root server.
- Android
:broker is a separate failure domain but shares the app UID; it is
not an adversarial-code security boundary.
- Fault injection and fail-stop hooks are DEBUG-only.
- No request is replayed after ambiguous loss; callers must reconcile unknown
outcomes at the application layer.
- iOS continues to advertise
hangRestartable=false until a bounded public
policy is qualified.
Observed platform/tool boundaries
- iOS public immediate recreation can reacquire stale process/active-channel
state while teardown is asynchronous.
- CoreDevice memory-warning injection returned
ENOENT; no synthetic result is
claimed.
- CoreSimulator lacks usable file-protection/headroom evidence and uses a
simulator-global Application Support root.
- Android evidence is one API 34 arm64 emulator image only; no physical Android
device was connected.
- Android requested socket-buffer size is not treated as an effective kernel
value. The backpressure result is based on readiness and write-progress
evidence instead.
Still unqualified
- Production progress-sensitive watchdog/retry policy and false-positive
behavior for legitimately slow SQL.
- Physical Android behavior, other API levels/ABIs/OEM images, and cross-device
dependability.
- Same-host long soak, multi-client/concurrent requests, lifecycle/Doze/app
standby/background behavior, LMK/jetsam, and leak/resource accumulation.
- Reboot, force-stop, update, reinstall, eviction, power loss, disk-full,
torn-write, and filesystem-corruption durability.
- General data integrity/exactly-once, CPU, energy, maximum throughput, memory
limits, and latency SLA.
- Distribution builds, TestFlight/App Store, Play distribution, and review.
Verification
iOS retained/current checks
- Swift static/package checks and 149/149 tests in nested and root manifests.
- iPhoneOS Debug and Release compile/link for host and extension.
- strict Swift formatting/parsing, seven broker-script Bash/ShellCheck gates,
Ruby generator syntax, SwiftPM renderer 5/5, docs 41 routes.
- PostgreSQL patch-stack/currentness, iOS embedded compile probe, and strict
native host smoke covering role/authentication, stream/cancel, persistence,
backup/restore, and native smoke twice.
Android current checks
:android-native-broker-spike:compileDebugKotlin PASS.
:android-native-broker-spike:testDebugUnitTest PASS: 7/7 tests.
- Canonical Android arm64 native
--check-current PASS for NDK
27.0.12077973.
bash -n, ShellCheck, both embedded Python validators, and focused
git diff --check PASS.
- docs product check PASS: 41 routes.
- Final emulator behavior series 10/10, plus post-document confirmation
PASS.
- Commit hooks passed before
4db5de94.
The broad repository/release gate has not been rerun from a clean checkout of
the final commit. The shared working tree still contains unrelated unstaged
release-tool and portability changes; none is in this commit.
Before this leaves draft
Uh oh!
There was an error while loading. Please reload this page.
Summary
Adds native-broker feasibility spikes for iOS and Android. Both move
liboliphaunt/PostgreSQL into a separate app-owned worker process, keep SQL bytes
off the control plane, classify post-dispatch loss as
outcomeUnknownwithoutSQL replay, and admit recovery only after a fresh process epoch and healthy
query.
This remains a draft feasibility PR. The evidence supports VIABLE WITH
LIMITATIONS, not production readiness or a cross-device reliability SLA.
iOS 26
OliphauntBrokerProtocol,OliphauntBrokerXPC,OliphauntIOSBroker, andOliphauntBrokerExtensionSwift products and therendered release-package graph.
AppExtensionProcessand XPC for control plus a framed AF_UNIX FD dataplane with explicit ownership, bounded input/raw collection, streaming,
cancellation, checkpoint/background controls, diagnostics, and crash
recovery.
extensions in the worker bundle; the host does not link liboliphaunt.
and hard stream-queue enforcement by splitting oversized backend output.
Android
android:process=":broker", a minimalAIDL/Bundle control plane, and
ParcelFileDescriptor.createReliableSocketPair()data plane.independent fail-stop scheduler, Binder death recipients, UUID worker
epochs, and lazy fresh-generation reconnect.
embedded PostgreSQL pthread with
SIGINT, preserving PostgreSQL's normalstatement-cancel handler and SQLSTATE
57014behavior.pg_sleep, post-commit/pre-completion ambiguity, persistent reopen, andcontrolled 8/32 MiB socket backpressure.
Shared contract
outcomeUnknown. The host never replays SQL automatically.Ready/handshake, and healthy SQL.
reopened after worker loss.
iOS retained qualification
The canonical August 10 reports exactly qualified their then-current source.
Later cleanup removed unsupported private diagnostic SPI and mechanically
formatted the staged source, so these are retained feasibility evidence rather
than exact-current-commit qualification.
recoveryProven=false: attempts 1→2, Ready 1→1, no recovered PID/epochCanonical physical total including the negative hang lane is 5 launches / 131
checks. PASS for that hang lane means its negative-evidence contract passed;
it is not recovery evidence.
iOS deliberate-hang findings
The first immediate-recreate test did not obtain a fresh healthy worker:
Hello timed out.
generation; retained evidence could not distinguish stale-PID reuse from
teardown still being in flight.
A stronger physical DEBUG matrix armed and acknowledged the fault, proved
same-epoch control responsiveness, then deadlocked an ordinary request:
Unsupported private terminate/unique-instance controls also recovered at zero
delay, but all private ExtensionFoundation SPI was removed. Initial private
terminate prototypes crashed because the experiment used the wrong Swift
calling convention; that SIGSEGV was an experiment bug, not an iOS result.
Apple exposes cancel/invalidate but no public force-fresh flag, PID/exit waiter,
or termination-completion barrier. XPC cancellation is asynchronous and
non-preemptive, and
AppExtensionProcessmay bind an existing process. Thepublic delay result supports asynchronous teardown/process reuse as the
immediate mechanism; it does not establish a documented 100 ms threshold.
hangRestartable=falseremains the advertised capability.iOS repeated fail-stop controls
On one physical iPhone 14 Pro / iOS 26.5 beta stack:
_exit(70)firedwhile the worker actor remained in a non-returning semaphore wait. Every
trial returned
outcomeUnknownat 1.003–1.068 s, then obtained a fresh PID,epoch, validated Ready generation, and healthy SQL.
abort()during
SELECT pg_sleep(60), .... Every trial returnedoutcomeUnknown,then obtained a fresh PID/epoch/Ready generation and healthy SQL.
SIGABRTon the fault-injectorglobal queue while other threads are simultaneously inside
oliphaunt_exec_protocol_streamand PostgreSQLpg_sleep. Crash-logcoalescing makes this direct corroboration for sampled trials, not all 60.
Under an IID assumption, 60/60 gives a one-sided 95% lower success bound of
95.13%. IID is weak for serial trials on one device/build, so this is
repeatability evidence for the DEBUG mechanism, not a shipping SLA or a
production progress-sensitive watchdog policy.
iOS memory and performance
samples each.
33,599,543 bytes in about 79.9 s, around 420 KB/s. This proves bounded
backpressure under the imposed reader rate, not maximum throughput.
memory was about 16.9 MB and 17.3 MB. Both runs preserved the required 8 MiB
margin and the native 8 MiB queue ceiling.
missing/mismatched/unavailable/unreadable/symlink entries.
CoreSimulator does not prove extension-private containment, file-protection
metadata, or available-memory headroom. Physical Class-C evidence covers
after-first-unlock, not locked-before-first-unlock behavior.
Android retained qualification
The final API 34 arm64 emulator behavior series is:
pr-doc-sync-v1is an additional PASS whose source manifest includes the finalREADME. The ten repeated runs used the same executable inputs; their only
source-file difference is the README evidence-path text. The final committed
spike files match the retained file manifests, but the APK was built from a
dirty, hash-manifested worktree rather than a clean checkout of commit
4db5de94.All ten full matrices passed the same 11 checks. They used ten unique host
PIDs and produced 40 unique worker PIDs and 40 unique UUID epochs.
outcomeUnknownpg_sleep+ independent SIGABRToutcomeUnknowncompletedoutcomeUnknownAll 30 faulted worker PIDs have exact retained
Fatal signal 6records.Binder-death events are scoped to the exact PID and epoch; first loss signal
varied safely between EOF,
binderDied, andonServiceDisconnected.Android native-path witness and no replay
The native fault executes one ordered query whose first Append child emits 513
8 KiB rows and whose second child runs
pg_sleep(60). The worker:All ten native lanes witnessed exactly 4,202,496 backend bytes, then
observed Binder death and fresh recovery. This strongly proves ordered native
PostgreSQL execution immediately before the sleeping plan child. It is not a
callback from inside
pg_sleep; that narrower claim remains unproven. Thereport field
nativeDispatchObservedis currently an alias for witnesspresence and is not used as independent evidence.
Every ambiguous post-commit loss had
ambiguousExecutionCount=1and derivedreplayCount=0. The committed marker survived all three broker deaths in everyrun. This proves no replay for the instrumented counter and persistent reopen
within each matrix; it is not a generic exactly-once protocol or reboot/power
loss qualification.
Android controlled slow-reader evidence
The host holds an explicit zero-read gate while Binder diagnostics sample a
blocking FD with
POLLOUT=false, the same synchronous response write inprogress for at least 300 ms, and unchanged completion counters. It then
releases the gate and drains the complete response from the same generation.
The cross-size bound delta was zero while response size quadrupled. This is
direct workload-specific evidence of synchronous socket backpressure, not an
effective
SO_SNDBUFmeasurement or a general process-memory bound.Memory rose substantially because the experimental host accumulates and
reports the final response:
33,157,120–33,964,032 B.
98,533,376–101,945,344 B.
Gross emulator drain rates include the deliberate gate and diagnostics and are
not maximum SQL/transport throughput or an SLA.
Android negative evidence that changed the harness
final-witness-001: PostgreSQL buffered a small first-statementCommandCompleteuntil after the sleeping statement, so it could not serveas a pre-hang witness.
final-output-witness-006: the first transient non-writable socket writeadvanced while the client still had not read. The final probe therefore
keeps a read gate closed and resets its candidate until the same sampled
write remains unchanged for at least 300 ms.
Explicit limitations
By design / experimental scope
production multi-client/root server.
:brokeris a separate failure domain but shares the app UID; it isnot an adversarial-code security boundary.
outcomes at the application layer.
hangRestartable=falseuntil a bounded publicpolicy is qualified.
Observed platform/tool boundaries
state while teardown is asynchronous.
ENOENT; no synthetic result isclaimed.
simulator-global Application Support root.
device was connected.
value. The backpressure result is based on readiness and write-progress
evidence instead.
Still unqualified
behavior for legitimately slow SQL.
dependability.
standby/background behavior, LMK/jetsam, and leak/resource accumulation.
torn-write, and filesystem-corruption durability.
limits, and latency SLA.
Verification
iOS retained/current checks
Ruby generator syntax, SwiftPM renderer 5/5, docs 41 routes.
native host smoke covering role/authentication, stream/cancel, persistence,
backup/restore, and native smoke twice.
Android current checks
:android-native-broker-spike:compileDebugKotlinPASS.:android-native-broker-spike:testDebugUnitTestPASS: 7/7 tests.--check-currentPASS for NDK27.0.12077973.bash -n, ShellCheck, both embedded Python validators, and focusedgit diff --checkPASS.PASS.
4db5de94.The broad repository/release gate has not been rerun from a clean checkout of
the final commit. The shared working tree still contains unrelated unstaged
release-tool and portability changes; none is in this commit.
Before this leaves draft
Qualifiedevidence.evidence is required.
expand API/OEM coverage.
recovery conservatively unadvertised.
qualification.
broker API as production-stable.