Skip to content

docs(compliance): audit posthog-react-native, posthog-flutter and posthog-android (64 rows each) - #120

Open
posthog[bot] wants to merge 5 commits into
mainfrom
posthog-code/weekly-sdk-compliance-matrix
Open

posthog[bot] wants to merge 5 commits into
mainfrom
posthog-code/weekly-sdk-compliance-matrix

Conversation

@posthog

@posthog posthog Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Why

Weekly bounded audit of the PostHog SDKs against the cross-SDK contracts in openspec/specs/, so the matrix reflects both new SDK code and the spec changes merged since the last run. ≤3 SDKs are deeply re-verified per run. This is the accumulating branch for that work — it carries two runs so far and should be read as one baseline.

Audited across this PR

Run SDK Commit Result Why selected
2026-10-19 posthog-react-native e1efa57 → 3ddaa654 28 ✅ · 28 🟡 · 3 ❌ · 5 ➖ · 0 ❓ (was 31/23/2/2/1 on 59 rows) Rule 2/4: stalest pointer, 802 commits of drift
2026-10-19 posthog-flutter 05b53dc → 1032c102 21 ✅ · 29 🟡 · 6 ❌ · 8 ➖ · 0 ❓ (was 20/22/6/5/6 on 59 rows) Equally stale; held all 6 Unknown cells in the matrix
2026-10-19 posthog-android 8659a7b4 → c5e788bd 35 ✅ · 18 🟡 · 5 ❌ · 6 ➖ · 0 ❓ (was 31/16/9/3/0 on 59 rows) Ten weeks stale; mobile reference for Session Replay Debug Properties
2026-10-12 posthog-python 95c7f6e0 → 509c8525 13 ✅ · 16 🟡 · 5 ❌ · 30 ➖ · 0 ❓ Rules 1+2
2026-10-12 posthog-node fbdb6c7b → 4f590b36 9 ✅ · 21 🟡 · 4 ❌ · 29 ➖ · 1 ❓ Rules 1+2

Ten of twelve SDKs now carry all 64 contracts. posthog-php and posthog-ruby sit at 63 (needing only the MCP Analytics row, N/A for both). posthog-java is the last SDK at 59 rows.

Notable movement — 2026-10-19 run

  • Every ❓ Unknown cell has been drained. posthog-flutter's six and posthog-react-native's one are all resolved to a definite status with direct code evidence. The one ❓ left in the matrix is a correction, below.
  • posthog-flutter is no longer only a wrapper. It now ships its own Dart core (~2,700 lines) driving a new Windows/Linux desktop platform, which is why its Unknowns became auditable. Two things in it lead the matrix: its Retry Queue keeps a transient-failure batch queued (the defect posthog-js/-node/-react-native all share), and it implements the malformed-payload rule with both the correct sentinel and the mandated diagnostic.
  • posthog-android is the largest net improvement so far — ✅ 31→35 while the denominator grew by five, ❌ 9→5. Screen, Shutdown, HTTP Client and Capture Exception all cleared outright. It also has by far the most complete Session Replay Debug Properties implementation outside the browser.
  • posthog-react-native: two improvements and four downgrades, none of the downgrades a code regression — they are all spec text that merged after its last audit (projectToken, and malformed-payload decoding on three flag getters).
  • A false negative corrected. The previous run recorded posthog-node's Traces row as ❌ on "no OTLP pipeline anywhere in the monorepo". The pipeline had merged four weeks earlier and posthog-node is the package that wires it. The row is moved to ❓, not to a Pass — the implementation exists, but scoring it against the traces spec needs a read this run didn't have budget for. Nothing else on that page was re-audited.

Needs a human decision

  1. The exception property-bag override is the matrix's most widely-shared non-compliance. exception-event-metadata line 300 forbids a caller's generic property map from overriding SDK-owned $exception_list/$exception_level/$exception_source. Six of twelve SDKs violate it — this run added react-native, flutter and android, and android's source documents it as intended. All six carry a "Needs deprecation path" verdict for the same reason: a typed override channel has to ship before the generic key stops winning. That sequencing is one cross-SDK decision, not six.
  2. MCP Analytics ownership (carried from the previous run): the spec's applicability table lists @posthog/mcp under posthog-js, but the package declares posthog-node as its workspace and peer dependency. Exactly one should own the row.

Rotation

All three slots used — the first full run since the rotation began slipping, and it clears the three oldest pointers at once. Six SDKs are queued; posthog-java is the clear #1 and the only SDK outside the ~4-week window. Because its posthog-server/ lives in the posthog-android monorepo this run just read, much of its evidence is already written. The previous run's contingency (a shallow row-completion pass over the four 59-row files) is no longer needed — three of those four were completed properly here.

Scope: documentation under compliance/ only — no spec content, acceptance files, or SDK code touched.


Created with PostHog Desktop

🤖 Generated with Claude Code

@posthog posthog Bot changed the title docs(compliance): re-audit posthog-python and posthog-node (64 rows each) docs(compliance): audit posthog-react-native, posthog-flutter and posthog-android (64 rows each) Oct 19, 2026
posthog Bot and others added 4 commits October 20, 2026 14:02
…ach)

Weekly bounded SDK compliance audit.

Audited this run (priority rule 1 + rule 2): posthog-python at 509c8525
(from 95c7f6e0) and posthog-node at 4f590b36 (from fbdb6c7b). Both were
the previous queue's joint #1, both are named by the server-side spec
changes merged since the last baseline, and both were short the two
newest contract rows, which this run adds — so both files now carry all
64 contracts.

posthog-python: 13 ✅ · 16 🟡 · 5 ❌ · 30 ➖ · 0 ❓ (was 14/12/7/29/0 on 62)
posthog-node:   9 ✅ · 21 🟡 · 5 ❌ · 29 ➖ · 0 ❓ (was 10/16/8/28/0 on 62)

Only two of the three available slots were used; the reasoning and the
resulting rotation-slippage warning are recorded in the index.

Co-Authored-By: Claude Opus 5 <[email protected]>

Generated-By: PostHog Desktop
Task-Id: 229000da-306e-48a3-a8c2-cf9021906a6c
…thog-android (64 rows each)

Three-slot run following the queue the previous run left. No spec change merged
since 2026-10-12, so selection fell to the staleness rules.

- posthog-react-native at 3ddaa654 (802 commits of drift): 28/28/3/5/0. Capture
  Exception cleared on the PR #103 withdrawal; Session Replay Privacy resolved
  from Unknown. Four rows downgraded purely on new spec text (projectToken, and
  malformed-payload decoding on three flag getters). Session Replay Debug
  Properties scores Fail on first audit.
- posthog-flutter at 1032c102: 21/29/6/8/0. The repo now ships its own Dart core
  driving a Windows/Linux desktop platform, so all six of its Unknown cells are
  resolvable from the repo — five to Partial, Retry Queue to Pass. Exception
  Event Metadata scores Fail on first audit.
- posthog-android at c5e788bd (targeted re-audit): 35/18/5/6/0, the largest net
  improvement so far. Screen, Shutdown, HTTP Client and Capture Exception all
  cleared; Autocapture improved. Matrix re-sorted alphabetically, so its note
  numbers are reassigned.

Also corrects posthog-node's Traces row from Fail to Unknown: the pipeline it
was recorded as missing had merged four weeks before that audit, and node is
the package that wires it. Not re-scored, because scoring it properly needs a
read this run did not have budget for.

The matrix now has ten of twelve SDKs at the full 64 rows and one Unknown cell
left, which is that correction. posthog-java is the only SDK outside the
four-week window and the only one still at 59 rows.

Co-Authored-By: Claude Opus 5 <[email protected]>

Generated-By: PostHog Desktop
Task-Id: a169426b-4e39-41b1-99bb-816a0ff89e14
…indings

- Downgrade posthog-flutter Get Feature Flag Payload and Get Feature Flag
  Result to partial: only the Dart core (Windows/Linux) drops malformed
  payloads; iOS/macOS go through posthog-ios, which returns the raw string.
  Flutter summary and README roll-up move from 21/29 to 19/31.
- Restore the 2026-08-06 findings that were reduced to "as previously
  recorded", re-verified at 1032c10 with fixed and retired items marked.
  Notes are renumbered for the two new payload notes.
- Point flutter's cross-references at posthog-android's new note numbers.
- Reconcile README: Shutdown pattern, Unknown-cell claims, applicable-row
  ratios for python/node, and per-SDK open-gap counts.
- Restore the #110 refused-connection wording in posthog-android's HTTP
  Client entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…Person Properties to pass

Their recorded gaps were fixed (#614) or retired (sdk-specs #103), and the empty
setPersonProperties call no-ops on every downstream path. Flutter: 22/28/6/8.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@turnipdabeets turnipdabeets self-assigned this Oct 20, 2026
@turnipdabeets
turnipdabeets force-pushed the posthog-code/weekly-sdk-compliance-matrix branch from 884ab8c to 9cae3c9 Compare October 20, 2026 18:02
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 20, 2026 19:47
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 20, 2026 19:47

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated advisory code review.

Comment thread compliance/posthog-android.md Outdated
- **SDK currently:** Multi-hook support is real (`PostHogBeforeSend.kt:8-16`,
`PostHogConfig.kt:484-516` stores hooks in a lock-guarded `MutableList`). Two bugs reconfirmed by
direct read this cycle in `posthog/src/main/java/com/posthog/PostHogStateless.kt`, method
`buildEvent` (lines 289-322): (1) **line 310** — `eventChecked = beforeSend.run(postHogEvent)`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Refresh the Android hook evidence without reversing fail-closed safety — The refreshed audit says every hook receives the original event and recommends keeping the last-good event after an exception, but at the newly advertised c5e788bd, PostHogStateless.kt:322-335 calls beforeSend.run(eventChecked), assigns its result, and logs/drops on an exception. Both alleged defects are therefore wrong for this revision; moreover, before-send-hook/spec.md:89 explicitly requires stopping the chain and dropping after a throw to prevent privacy-filter leaks. Re-scoring against the old implementation and recommending fail-open would undo a compliant safety fix. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_android_hook_chain_matches_pinned_source fails on the reviewed head, and the compiled AndroidHookProbe.kt confirms replacement chaining and throw-driven drop/stop.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: re-scored ✅, removed fail-open advice.

Comment thread compliance/posthog-react-native.md Outdated
- **Backwards compatibility:** Backward-compatible — a net-new, additive feature; adding it cannot break any existing integration.
- **Remediation:** Implement the shared span/queue/OTLP-JSON logic once in `@posthog/core`, mirroring the architecture the `logs` pipeline already established there (buffered queue, batching, `AppState`-aware mobile flush triggers), so node, browser, and react-native can all consume it from one place.
- **Spec requires:** Dedupe `$feature_flag_called` per (flag, value), re-fire on value change, clear on `reset()` and shutdown, and honour the minimal-envelope allowlist.
- **SDK currently:** Re-read at this commit. The tracker (`flagCallReported`, `packages/core/src/posthog-core.ts:62`, read at `:1047`, written at `:1079-1080`) dedupes per `(flag, value)` correctly and is cleared on both `reset()` (`:151`) and `_shutdown()` (`:209`, in a `finally`). The minimal-envelope path is implemented in the stateless base (`minimizeFlagCalledEventProperties`, applied at `posthog-core-stateless.ts:569-575`). **Gap, unchanged:** the client never persists the server's `minimalFlagCalledEvents` gate, so `isMinimalFlagCalledEventsEnabled()` (`posthog-core-stateless.ts:645-647`) returns the fail-safe `false` for react-native and the minimal envelope is never produced, even when the project is gated into it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Remove the nonexistent React Native minimal-gate gap — The newly stated remaining gap says the client never persists minimalFlagCalledEvents and inherits the stateless fallback, but the pinned packages/core/src/posthog-core.ts:894-900 stores res.minimalFlagCalledEvents === true, and :967-968 overrides isMinimalFlagCalledEventsEnabled() to read that persisted field. Failed/quota-limited loads also preserve the previous gate. Thus the advertised source already implements the proposed remediation; this replacement finding and the corresponding index narrative direct maintainers to duplicate existing functionality rather than report the actual remaining tracker gaps. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_react_native_persists_minimal_gate fails on the reviewed head, while node RNMinimalGateProbe.ts demonstrates the pinned storage/reader path enabling minimization.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: gap removed, re-scored ✅.

Comment thread compliance/posthog-flutter.md Outdated

### n10 — Feature Flag Called Tracker (🟡 Partial)
- **Spec requires:** Dedupe `$feature_flag_called` per (flag, value), re-fire on value change, clear on `reset()`, and honour the minimal-envelope allowlist.
- **SDK currently:** **Resolved from ❓ Unknown.** The Dart core implements the tracker directly: `_flagCallReported` is a `Map<String, Set<PostHogFeatureFlagValue?>>` (`posthog_core.dart:25-26`), the dedup check is `!(_flagCallReported[key]?.contains(flagValue) ?? false)` (`:780`) — keyed on the value, so a changed value correctly re-fires — the set is populated after reporting (`:815`), `$feature_flag_called` is captured at `:838`, and the whole map is cleared on reset (`:82`). Failure cases are recorded for the event (`:888`). **Why 🟡:** desktop only; the mobile path is the embedded native tracker, which `compliance/posthog-android.md` n8 records as 🟡 (posthog-ios is ✅), and no minimal-envelope allowlist handling is visible on the Dart path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Account for Flutter's existing desktop minimal-envelope implementation — The resolved tracker audit claims no minimal-envelope allowlist exists in Dart and asks for one to be added. At 1032c102, core/feature_flag_utils.dart parses the server gate and defines minimalFeatureFlagCalledProperties; posthog_core.dart:652 persists it and :120-121 reads it, while posthog_core_stateless.dart:229-236 applies the allowlist specifically to non-experiment flag-called events. The same false absence is used in the Remote Config note and index. Any mobile gap may still justify Partial, but the desktop evidence and remediation need correcting rather than prescribing an already-shipped feature. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_flutter_minimal_allowlist_exists fails on the reviewed head, and dart FlutterMinimalProbe.dart confirms the pinned parser/filter removes custom properties while retaining attribution.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: credited the Dart allowlist; stays 🟡 only on the embedded Android tracker.

Comment thread compliance/posthog-flutter.md Outdated
| 45 | Reset | ✅ | |
| 46 | Reset Group Properties For Flags | ✅ | |
| 47 | Reset Person Properties For Flags | ✅ | |
| 48 | Retry Queue | ✅ | |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Do not award Flutter an all-platform Retry Queue pass from desktop evidence alone — The file explicitly grades every row across native, desktop and web, but this new Pass is justified only by Dart _doFlush. Flutter Web forwards captures to window.posthog; the browser implementation at the matrix's recorded 6538babc uses its separate RetryQueue, whose retry-queue.ts:94 excludes HTTP 429 from retries. A Flutter Web event rejected with 429 is therefore discarded rather than retained, contradicting the retry-queue contract even though the desktop 503 path is correct. The all-platform row and roll-up should retain the inherited web gap. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_flutter_retry_status_accounts_for_web fails on the reviewed head, and node BrowserRetryProbe.ts produces zero retry entries for 429 with one entry for its 503 control.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: Retry Queue now 🟡 for the Flutter Web 429 gap (n38).

Comment thread compliance/posthog-react-native.md Outdated
- **Spec requires:** Walk element ancestry on a touch/click, build a sanitized `$elements` array, emit `$autocapture` with `$event_type` and screen context; respect no-capture markers; never capture sensitive control values.
- **SDK currently:** `packages/react-native/src/autocapture.tsx` (273 lines), dispatched from `PostHogProvider`'s `onTouchEndCapture` into `posthog.autocapture(eventType, elements, …)` (`:267`). `ph-no-capture` is correctly honoured per ancestor (`:177`, configurable via `noCaptureProp`). **Improved since the last audit:** `eventType` is no longer a hardcoded literal — it is now a typed `'touch' | 'click'` parameter (`:127`, `:143`), though it still **defaults to `'touch'`** rather than the cross-SDK `'click'` vocabulary, so the default-path deviation stands. **Unchanged gap:** no defence-in-depth for secure/password inputs — grep for `secure`/`password` across `autocapture.tsx` still returns zero hits; only the generic `propsToCapture` allowlist (`:150`) gates capture, with no explicit exclusion for a `TextInput` with `secureTextEntry`.
- **Backwards compatibility:** Backward-compatible — the parameter already exists, so changing the default is a value change warranting a changelog note; a `secureTextEntry` skip is purely additive.
- **Remediation:** Consider flipping the `eventType` default to `'click'` with a changelog note; add an unconditional skip for `secureTextEntry`/password-like props regardless of `propsToCapture`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Keep the mandated mobile autocapture event type — The refreshed note still treats the mobile default touch as noncompliant and recommends changing it to click. Both the advertised spec snapshot and the reviewed autocapture/spec.md:98 explicitly mandate $event_type: touch for Android/iOS/React Native mobile taps and reserve click for browser interactions, including React Native Web. Following this remediation would introduce the contract violation the audit is supposed to prevent; remove the default-type gap and distinguish mobile from web when describing the remaining privacy issue. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_react_native_touch_remediation_obeys_contract fails on the reviewed head because the proposed default contradicts the explicit mobile requirement.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: removed the 'click' remediation.

Comment thread compliance/posthog-android.md Outdated
### n4 — Get Feature Flags (❌ Fail)
### n2 — Autocapture (🟡 Partial)
- **Spec requires:** Walk element ancestry on a UI interaction, build a sanitized `$elements` array, emit `$autocapture` with an `$event_type` and screen context; respect no-capture markers; never capture sensitive control values.
- **SDK currently:** **Improved from ❌ Fail — `$autocapture` now genuinely exists.** The previous audit found no generic UI-interaction autocapture at all. `PostHogElementInteractionIntegration` (`posthog-android/src/main/java/com/posthog/android/internal/PostHogElementInteractionIntegration.kt:186`) now captures `$autocapture` for actionable View and Compose taps, carries `$touch_x`/`$touch_y`, and additionally detects and emits `$rageclick` (`:178-182`). A no-capture marker is honoured per subtree via the `com.posthog.android.R.id.posthog_autocapture_no_capture` tag (`internal/InteractionTarget.kt:179`), documented at `PostHogAndroidConfig.kt:77-82`. **Why 🟡 rather than ✅:** the feature is **off by default** — `captureElementInteractions` defaults to `false` (`PostHogAndroidConfig.kt:77`, "Default: false"), so an app that does nothing gets no autocapture, unlike the browser SDK where it is the default-on behaviour the contract describes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Do not invent a default-on Android autocapture requirement — This new note identifies opt-in as the sole reason the implemented Android feature is Partial and asks maintainers to enable it by default or seek a carve-out. The canonical autocapture/spec.md:15 already says option names and defaults vary by platform, its behavior applies “when enabled,” and its acceptance scenarios explicitly initialize autocapture enabled. Default-off is not a contract gap; this invented requirement would encourage an unnecessary collection/volume change while leaving actual shape/privacy validation unfinished. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_android_default_is_not_a_contract_gap fails on the reviewed head against the contract's explicit platform-default allowance.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: default-off isn't a gap, re-scored ✅.

Comment thread compliance/posthog-android.md Outdated
- **Spec requires:** `client`. Four required keys on **every** captured event except `$snapshot` — `$recording_status`, `$sdk_debug_replay_event_trigger_status`, `$sdk_debug_replay_linked_flag_trigger_status`, `$sdk_debug_replay_internal_buffer_length`. Everything else is an **optional bundle** that goes **only on SDK events** (names starting with `$`, excluding `$feature_flag_called`, `$snapshot`, and the browser's `$$heatmap`) and **at most once every 30 seconds per SDK instance**. The queue-depth key sits outside both tiers and stays on every non-`$snapshot` event. `$snapshot` carries none of them. An SDK with no replay integration produces only `$recording_status: disabled`. **On mobile, a crash-time `$exception` reported on a later launch is stamped from a crash-context snapshot that always carries the full bundle.**
- **SDK currently:** Audited from scratch, and this is the most complete implementation of the contract outside `posthog-js`. `PostHogReplayIntegration.debugProperties()` (`posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt:2591-2643`) produces all three cross-platform `$sdk_debug_replay_*` required keys plus a genuine tri-state `$recording_status` (`disabled`/`buffering`/`active`) derived from a real buffering snapshot, and adds a thoughtful optional set: `$sdk_debug_replay_flush_hold_reason` (distinguishing `awaiting_remote_config` from `below_minimum_duration`), `$sdk_debug_replay_pending_trigger_conditions`, `$sdk_debug_replay_capture_mode`, and `$sdk_debug_replay_throttle_delay_ms`. The trigger statuses use a shared `triggerStatus()` helper with a correct `trigger_disabled`/`trigger_pending`/`trigger_activated` vocabulary. The wiring in `PostHog.sdkDebugProperties()` (`PostHog.kt:782-810`) is careful: it is applied only inside the `appendSharedProps` block, which `$snapshot` events skip (`:918-919`), so **the `$snapshot` exclusion is correct**; it is merged **after** the caller's properties so SDK-computed values win, with a comment citing posthog-js's `extend(properties, sdkDebugProperties)` as the model; the replay handler's contribution is guarded separately from the session and queue keys so a throwing handler cannot suppress them, surfacing `$sdk_debug_error_capturing_properties` instead; and it falls back to `$recording_status: disabled` when no replay handler is installed, exactly as the spec's no-integration case requires. `$sdk_debug_pending_queue_size` is attached as the queue-depth key.
**Two real deviations.** (1) **No tiering and no throttle.** The spec puts everything beyond the four required keys into an optional bundle restricted to SDK events and rate-limited to once per 30 seconds per instance. Android attaches the *entire* set — `$sdk_debug_replay_capture_mode`, `$sdk_debug_replay_throttle_delay_ms`, `$sdk_debug_replay_pending_trigger_conditions`, `$sdk_debug_replay_flush_hold_reason`, `$sdk_debug_session_start`, `$sdk_debug_current_session_duration` — to **every** event with shared props, including custom (non-`$`) events, with no throttle window anywhere. There is no `REQUIRED_REPLAY_PROPERTIES`-equivalent split. That is a per-event payload cost on every customer event, which is the cost the tiering exists to avoid.
(2) **The crash-context case does the opposite of what the spec says.** `sdkDebugProperties()` opens with an early return of an **empty** map when an explicit timestamp predates the current session start (`PostHog.kt:787-791`, "An explicit timestamp from before this session started (a previous-process crash reported on this launch) did not occur here"). That is precisely the mobile crash-time `$exception` the spec singles out — and the spec says it SHALL be stamped from a crash-context snapshot carrying the full bundle, not left bare. The code's reasoning is sound in isolation (today's live state is not the crash's state), but the spec's answer is to persist the snapshot at crash time and replay it, not to omit the keys.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Respect the previous-process crash exception to debug-property attachment — The new audit labels the empty debug map for a previous-process crash “the opposite of what the spec says.” The cited contract actually says at session-replay-debug-properties/spec.md:973-977 to attach a persisted crash-time snapshot if one was captured, otherwise attach none, and explicitly gives this rule precedence over required every-event keys; its scenario at :1012-1018 expects precisely that omission when no snapshot exists. Android's early return prevents attaching today's live state and does not itself prove the alleged violation. Remove this deviation unless evidence demonstrates that an available persisted snapshot is being lost; the separate tiering/throttling gap is unaffected. Reproduction: reproduced — python3 review_checks.py AuditTruth.test_android_previous_process_omission_is_permitted fails on the reviewed head because the audit condemns an outcome the normative scenario requires.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d89492a: removed that deviation; stays 🟡 on tiering/throttling.

@marandaneto
marandaneto requested a review from a team October 21, 2026 14:29
Android: Before Send Hook and Autocapture re-scored to pass (hooks chain and
drop on throw as the spec requires; default-off autocapture is allowed), and
the previous-process crash omission in Session Replay Debug Properties is
recorded as conforming.

React Native: Feature Flag Called Tracker re-scored to pass (PostHogCore
persists and reads the minimal gate); the 'touch' autocapture default is
correct for mobile taps.

Flutter: the Dart core already implements the minimal flag-called envelope;
Retry Queue moves to partial because Flutter Web inherits posthog-js's 429 gap.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants