Skip to content

Release v1.3.0 - #480

Merged
MP2EZ merged 185 commits into
mainfrom
development
Sep 9, 2026
Merged

Release v1.3.0#480
MP2EZ merged 185 commits into
mainfrom
development

Conversation

@MP2EZ

@MP2EZ MP2EZ commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Release v1.3.0 (build 6) β€” 88 commits since v1.2.1.

Features

  • FEAT-570 replace Sentry's feedback widget with an in-hierarchy bug-report form
  • FEAT-570 refuse and release the root overlay slot on screen-owned 988 routes
  • FEAT-288 date-range filter for journal re-read (Slice C)
  • FEAT-567 pin the Epictetus digitization and repair four mis-attributed passages
  • FEAT-569 close the radical-acceptance doctrinal traps and encode them
  • FEAT-580 give Letters 107.11 its affective complement and fix the attribution

Fixes

Crisis reachability & 988 affordances

  • DEBUG-559 stop the consent grant remounting the crisis subtree
  • DEBUG-557 pin the consent-grant navigator remount (AC-1)
  • DEBUG-575 release the root slot on entry to a crisis route (finding 2)
  • DEBUG-575 move root-slot focus trap off the overlay onto the navigator
  • DEBUG-547 move both Home rows out of the crisis FAB's contested column
  • DEBUG-533 rule the Sentry feedback widget a zero-988 window, harden the shake
  • DEBUG-533 record the on-device measurement behind the ruling (AC 1)
  • DEBUG-577 measure the share-sheet occlusion, record the purchase-sheet blocker
  • DEBUG-586 import the crisis band instead of re-deriving it
  • DEBUG-519 guard the two continue taps DEBUG-465's ruling never reached
  • DEBUG-519 record the measured bounds, which correct the derivation
  • DEBUG-560 crisis contact lines are one text flow, not a fixed 80pt column
  • DEBUG-524 pin voice-journal record-tap process liveness
  • DEBUG-516 pin the save control against the live keyboard frame

Privacy & analytics

  • DEBUG-534 privacy policy named an analytics-deletion control that does not exist
  • DEBUG-539 destroy the analytics identity on account erasure
  • DEBUG-545 make the account-deletion attestation durable
  • DEBUG-536 restore six feature-usage trackers INFRA-552 pruned
  • DEBUG-536 wire all six restored trackers to real call sites
  • DEBUG-549 delete the redundant autosave subscription
  • DEBUG-553 correct phiDetection's docblocks to describe the armed control

Assessment, accessibility & layout

  • DEBUG-550 refuse to score a malformed answer set, and stop stranding the user
  • DEBUG-527 completed practice card no longer fails WCAG AA
  • DEBUG-548 announce the Home card description to VoiceOver
  • DEBUG-562 derive the tab bar height from the bottom safe-area inset

Content authority

  • DEBUG-582 make seneca-letters-13 verbatim Gummere and pin the digitization
  • DEBUG-585 name the Loeb edition Gummere translated, not the Penguin selection

Build & release tooling

  • DEBUG-589 record the device gate as unavailable and pin the Maestro version
  • DEBUG-573 fail the install when a patch does not apply
  • DEBUG-511 honour ACK on a close that died without a verdict
  • stop release.yml reporting green for a failed build

Chores

  • INFRA-373 on-device breathing frame probe (Slice D complete) β€” pure accumulator, HUD scoping pin, both control runs recorded
  • INFRA-512 / INFRA-523 / INFRA-554 adversarial crisis-text corpus; the free-text zero-false-negative claim corrected and six verified misses landed
  • INFRA-535 / INFRA-558 PHIFilter differential harness, frozen baseline, and whitelist amendment procedure
  • INFRA-542 / INFRA-552 analytics wiring (app_opened / app_backgrounded), orphan constants pruned, SAFE_EVENT_TYPES derived
  • INFRA-568 rotate session_id instead of claiming a rotation it never performed
  • INFRA-576 fix three un-allowlisted CVEs blocking every PR
  • INFRA-571 add a call-site rule for third-party full-screen presenters
  • INFRA-532 / INFRA-420 gate coverage for WeeklyReflectionComposer and the guidance tier cap
  • MAINT-564 daily loop presentation β€” virtue chip grid and header progress bar
  • MAINT-528 / MAINT-566 / MAINT-513 practice card spacing, evening placeholder copy, App Store Server API hosts

Release-time notes

  • js-yaml GHSA-2883-xcg3-v3hh (HIGH, published mid-release) was cleared in the bump PR by moving the existing overrides/resolutions pins to 3.15.2 / 4.3.2. Build-time consumers only.
  • Main Branch Health main branch CI health failing (2026-09-01) β€” audit gateΒ #456 β€” main could not pass its own Security + compliance gate. This release is that issue's documented carrier and should clear it.
  • being.fyi was serving privacy policy v1.10 against development's v1.11; the website deploy was re-run before this release, and Legal Site Freshness now passes.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh

MP2EZ and others added 30 commits August 22, 2026 20:25
The ACK check sat inside the DONE branch, so it was only reachable for a
run that finished. A run that is killed or loses its host never writes
DONE: it ages into the STALE arm, which consulted nothing. That entry
reported forever and held rc=1 forever, and `touch <dir>/ACK` could not
clear it.

Both /b-work Step 0.3 and /b-close gate on this exit code and tell the
operator to stop and handle a non-zero result, so one unclearable entry
stops every later session with an already-handled line β€” until the check
becomes noise, and the next genuine failed close lands in a channel
nobody reads.

Read ACK above the DONE branch and apply it to the DONE and STALE arms.
The in-flight arm deliberately IGNORES it (AC3): that arm adds nothing to
rc, so an ACK could only hide a live run β€” and refusing instead would
rebuild this bug mirror-imaged, as an entry the documented remedy cannot
clear. Stated in the header comment, which claimed the contract this
code broke (AC5).

The STALE line now names its remedy, as the DONE line already did; its
absence is why the observed case was cleared by hand-writing a DONE file.

Tests pin all four {DONE, no DONE} x {ACK, no ACK} states plus the
in-flight carve-out, and that the mute is per-directory β€” silencing the
whole sweep would satisfy AC1 and break AC2.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
journal-save-button sat 100% under the keyboard at extra-extra-extra-large
(measured [24,415][279,473] against a full keyboard window of [0,407][375,667]
on iPhone SE 3), so a large-text user could not reach the app's only save-time
crisis scan at rest. The keyboard band is identical at every text size; only
Save moved, pushed down by the heading and transcript growing above it.

DEBUG-480 fixed the default size only and left 4pt of clearance β€” one Dynamic
Type step from failing. It failed.

Restructure: the review phase's action block leaves the ScrollView and becomes
a plain flex sibling of it, inside an outer container whose paddingBottom is
read from the LIVE keyboard frame. Clearance is now non-negative by
construction rather than by arithmetic.

Frame-derived deliberately, and this is the load-bearing choice. UIKit reports
key layout + predictive bar + any inputAccessoryView as one union, so the
inset IS the occluding edge rather than a proxy for it. DEBUG-506 is about to
attach this app's crisis accessory for the first time and raise that edge ~72pt
on this surface, which would take DEBUG-480's 4pt clearance negative at the
DEFAULT text size. A clearance-derived fix is correct under exactly one of
those two states; this one is correct under both, because the inset and the
edge are the same number read once.

Not useOverlayBottomInset(): its MAX with CRISIS_BUTTON_RESERVED_BAND reserves
176pt with no keyboard up β€” a quarter of an SE 3 viewport, in every phase, to
duplicate protection the action row's paddingRight already provides. The band
is the shape for a centred card; a bottom-anchored row dodges the crisis button
horizontally. The keyboard subscription is extracted to useKeyboardFrameHeight
rather than forked a third time, on the same module-level useSyncExternalStore
shape as useKeyboardOccludesCrisisButton.

Also here, because the restructure causes them:
- the save error moves into the pinned footer and its scrollToEnd is retired.
  It can no longer push Save down, and left in place it would fight the
  scroll-to-top below. It gains accessibilityLiveRegion (WCAG 4.1.3) β€” the
  scroll was its only handling and that signal is visual.
- the banner scrolls back to top on the crisisActive rising edge. In a
  full-screen ScrollView "first child" and "top of viewport" coincided; in a
  shrunken scroll region they do not, and the one path that can disclose while
  scrolled is a save that fails and discloses.
- the transcript floor is expressed in scaled lines, not a fixed 180pt, which
  held ~7 lines at default and ~2 at AX5 on a screen whose task is proofreading.
- journal-discard-button relocates out of the footer at fontScale >= 2.6 so it
  never crowds Save. Relocation, never deletion; Save never moves.

Constraints from the crisis and accessibility planning passes, both of which
withdrew positions once given the DEBUG-506 fact and each other's arithmetic:
no second 988 control on this surface (that obligation is DEBUG-506's and is
app-wide), keyboardDismissMode stays on-drag while DEBUG-506 is open, and
journal-review-header stays inside the scroll region so its bubble-phase
dismissal keeps working.

Corrected in place: the automaticallyAdjustKeyboardInsets comment, which
described that prop as the reachability mechanism. It insets scroll content and
does not move contentOffset, so it never repositioned Save and cannot reach a
pinned footer. Its accessory-inclusiveness claim was also never exercised β€” no
build has produced a non-zero bar for it to absorb.

Refs DEBUG-480, DEBUG-507, DEBUG-506

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
…e URL

Swap APP_STORE_SERVER_API_ORIGINS to the short forms Apple currently documents
(api.storekit.apple.com / api.storekit-sandbox.apple.com), and move the two
literals that pin them in the same commit.

Future-proofing, not a live defect. Measured 2026-08-22: each pair still shares
one Akamai CNAME (commercegateway / commercegateway-sandbox) and all four hosts
return 401 unauthenticated, so the swap is cosmetic today. It guards against
Apple retiring the legacy alias, which would fail every Apple verification at
once -- verify-apple-receipt and grace-period-automation both resolve the host
through the same resolveApiOrigin.

The pinning test's failure on the constant change alone (162 passed / 1 failed)
is the proof it was load-bearing; that exactly one test failed also confirms the
other four references track the constant symbolically and needed no edit.

Also asserts the distinctness the test's name has always promised but never
checked, so a future edit cannot collapse the two hosts without failing.

No fallback and no host-switching retry: the module header forbids it by name,
because that is the deleted 21007 sandbox-fallback bug. Two hosts co-resolving
is the reason no fallback is needed, not a reason to try both.
resolveApiOrigin's no-default fail-closed throw is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
AC 4. profile-voice-reflection-xxxl.yaml stopped at the keyboard-up state
because the next step was broken; DEBUG-516 fixed it, so the flow regains
the contract it was always meant to pin: tap journal-save-button at its
resting position, keyboard up, at the largest non-accessibility text size,
and assert journal-saved-state.

No scroll before the tap and no visibility assertion on the button β€” both
are forbidden in this file and in journal-crisis-scan.yaml. A scroll weakens
the contract from "reachable with the keyboard up" to "reachable after
scrolling", and the iOS keyboard is a separate window so XCUITest scores a
100% covered button fully visible. The oracle stays journal-saved-state,
which only handleSave's success branch produces.

Also adds a drag-dismissal block, deliberately as a separate capture at the
end. keyboardDismissMode='on-drag' is this surface's only keyboard dismissal
(multiline field, so Return inserts a newline and there is no Done key), and
while DEBUG-506 is open it is also the only route back to a state where the
root crisis button is reachable. The restructure shrank the scroll region, so
"the content still scrolls, therefore the drag still dismisses" stopped being
free β€” if it ever stops being draggable the mitigation dies silently. It
cannot share the block above, because `swipe` destroys the condition that one
tests.

Both files' recorded geometry is stamped as PRE-DEBUG-506 evidence rather than
a threshold. The +4pt @ large / -37pt @ XXXL figures record where an
in-ScrollView Save landed on one build, and two things move them: type size
(which is DEBUG-516) and the keyboard's own chrome β€” an attached
inputAccessoryView joins the union UIKeyboardFrameEndUserInfoKey reports, so
DEBUG-506 raises the occluding edge ~72pt and would have taken the 4pt
negative at the default text size. The fix no longer derives from either
number.

Refs DEBUG-480, DEBUG-507, DEBUG-506

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
…pi-hosts

chore: MAINT-513 App Store Server API host constants lag Apple's documented base URL
…s terminal

The block added in the previous commit ran after journal-saved-state and
started a fresh capture. It failed on device with `Element not found:
journal-record-button`: the `saved` phase is terminal by design and offers no
affordance back to `idle`, which VoiceReflectionScreen's own DEBUG-504 comment
states outright.

Folded into a single capture, ordered drag -> re-raise -> save. That is
stronger than the original shape, not a workaround: the save is now measured
against a freshly-presented keyboard AND a scroll region the drag has already
moved, so it is the pinned footer rather than a lucky content offset that has
to keep Save reachable.

Drags DOWN from the header rather than UP β€” at the top of the region that is an
overscroll bounce, which still begins a drag (so on-drag fires) without
carrying the transcript field off screen and stranding the steps below it.

The comment records why a route back to `recording` must never be added to
"fix" this: anything reaching that phase outside handleStart inherits the
previous capture's draftId and silences its disclosure, which is the defect
DEBUG-504 fixed.

Refs DEBUG-504

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
…ne generator

Lands the measurement apparatus for free-text crisis detection. Scoped at batch
approval: the harness, fixture schema and generator ship now; the LLM corpus
generation is deferred (no ANTHROPIC_API_KEY in this environment, and no root
package.json, so the SDK cannot be installed without breaking FEAT-287's pin).

Four artifacts, all test-only -- textCrisisDetection.ts is not modified, so there
is no runtime behaviour change and no safety-surface diff:

- scripts/generate-crisis-corpus.js -- zero-dependency, repo-root, raw HTTP against
  the Messages API. Wired into nothing: no npm script, no CI job, no test. Refuses
  to run without a key rather than degrading to an empty corpus, and REFUSES an
  --out path inside the repo (unreviewed generated phrasing must not enter git
  history, and an untracked file under the worktree invalidates the e2e provenance
  fingerprint). Both refusals verified by running them.

- crisisTextCorpus.v1.json -- seed corpus. JSON, not .ts, because jest's testMatch
  collects any .ts under a src __tests__ dir and would fail it as an empty suite.
  Four classes; KNOWN_OVER_FIRE exists so the deliberate "I don't want to die"
  over-trigger cannot be mislabelled MUST_NOT_FIRE to improve a rate.

- textCrisisDetection.corpus.test.ts -- reports MUST-FIRE recall, MUST-NOT-FIRE
  false-positive rate and STT-MANGLED recall as three separate figures, and
  thresholds none of them (AC5: recorded, not enforced). It hard-fails only on
  corpus integrity and on an ANCHOR set -- one canonical phrase per shipped
  pattern -- which is the anti-narrowing lock. Verified by mutation: removing the
  no-point-living pattern turns three assertions red; reverted.

- The dated review packet, which is the record, because the CI job that selects
  this file runs --silent and swallows the console figures.

The seed's 12/12 recall is a HARNESS SELF-TEST, not a baseline -- every seed item
is a string already reviewed and committed in textCrisisDetection.unit.test.ts.
The packet says so in three places, and carries six VERIFIED MUST-FIRE misses as
the first candidate batch: "killing myself", "ending it all", "cant go on",
"suicide", "i want to end my life", "i wish i was dead" all MISS, and all are
correctly-transcribed text -- which falsifies the module header's "zero-false-
negative holds over correctly-transcribed text". Correcting that header is
deliberately out of scope here (it would re-tier this to an attended close) and is
the most important thing to carry forward.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
…a pre-existing abort

MEASURED on the gate simulator, 3 of 3 runs: ExpoSpeechRecognizer.start ->
prepareMicrophoneRecognition -> AVAudioEngine.inputNode -> AURemoteIO::Cleanup
-> _ReportRPCTimeout -> abort(). A blocking RPC to the simulator's audio
daemon, entered on the journal-record-button tap, SIGABRTing the app ~15s later
wherever the flow has reached.

NOT a DEBUG-516 regression: crash reports carrying this exact stack predate
this branch (2026-08-21 02:14, 02:20, 02:22, 02:26; 2026-08-22 04:25, 04:31),
and nothing in this item touches handleStart or the speech guard. What the
added block did was lengthen the record -> assert window past ~15s, so the
abort started landing inside the flow instead of after it. It surfaces as
"App crashed or stopped while executing flow" attributed to whichever step was
executing β€” in one run it surfaced instead as a bare "Element not found",
so a green run is not evidence the audio path was healthy.

The flow keeps AC 1 and AC 4 and nothing else, so the whole record -> save ->
journal-saved-state sequence sits inside the window. The header records the
bound, so nothing is inserted there later without re-measuring it.

Recorded as a STATED coverage gap rather than pinned green by a weaker
assertion: that keyboardDismissMode='on-drag' actually dismisses is now
unverified on device. It matters because it is this surface's only keyboard
dismissal and, while DEBUG-506 is open, the only route back to a state where
the root crisis button is reachable.

The abort itself needs its own item; it is a product-visible crash on the
record path, not a harness defect.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
…-corpus

chore: INFRA-512 adversarial crisis-corpus harness, fixture and offline generator
check:ci-test-coverage failed on src/core/hooks/__tests__/useKeyboardFrameHeight.test.tsx:
it matches no CI --testPathPattern, so it would run on nobody's pull request.

Declared rather than wired, following the ledger's own convention. Its consumer's
suite β€” VoiceReflectionScreen.behavioral.test.tsx β€” is already listed for exactly
the same reason (co-located under src/**, reached by no pattern), and wiring the
remainder is a deferred follow-up blocked on the assessmentStore load-dependent
test recorded in wiring-them-is-deferred. Inventing a pattern for one file would
change the CI contract for every PR in the repo, which is not this item's scope.

NOT fixed by renaming toward a pattern β€” that is forbidden by the ledger's own
do-not-fix-by-renaming entry, and coverage-by-filename is the defect INFRA-368
exists to document.

The why entry records what the absence actually costs, so the next reader does
not have to re-derive it: the behavioural contract IS gated (Phase 2.5 runs
journal-crisis-scan on any features/journal change), so what is missing is the
subscription-shape and snapshot pins, not the reachability contract.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZaGesrhCZM64DRoA7DeXh
Enumerated permission pins cannot catch the failure that rejected v1.2.0.
app/package.json still declared expo-file-system ~56.0.7; the lockfile's
RESOLVED version moved 56.0.7 -> 56.0.9, which added a PHPhotoLibrary call,
and Apple requires NSPhotoLibraryUsageDescription whenever the symbol links.
No diff in the repo showed it.

This scans each package's own ios/ sources for purpose-string APIs and fails
closed on any that is present but undeclared in app.json β€” inverting
speechRecognitionPermissions.config.test.ts, which asserts a hand-written list.

Exemptions are explicit and carry a reason. One today: expo-sensors, whose
CMPedometer reference is unreachable (only useBugReportShake.ts consumes it,
via Accelerometer/CMMotionManager, which needs no purpose string).

Includes the guards a source-shape assertion needs to stay honest: a
non-trivial-scan floor, a known-bad literal the matcher must still match, and
a check that every exemption names a module that still ships native sources.
Verified red by deleting the key: names 'NSPhotoLibraryUsageDescription
(required by: expo-file-system)'.

Runs in test:safety, so precommit and the CI Safety + privacy gates job.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
eas build --no-wait exits 0 when EAS *accepts* the job, so the workflow's
conclusion described scheduling, not the build. v1.2.0 errored in Install
dependencies on an expired GITHUB_TOKEN while this job reported success β€”
nothing on GitHub ever went red, and the failure was found only by reading
eas build:list an hour after the release was tagged.

Drops --no-wait so the job carries the build's verdict, and raises
timeout-minutes 15 -> 60 to cover queue + build + submit (15 was budgeted for
the fire-and-forget exit; keeping it would have failed the job on success).

Adds a closing step stating what green does not mean: Apple can still reject
the uploaded binary during processing and reports only by email β€” ITMS-90683
rejected v1.2.0 minutes after a fully green submit. No workflow can gate that,
so it is said out loud instead of implied away.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
…tale

fix: DEBUG-511 honour ACK on a close that died without a verdict
chore: harden the release path against false greens
…d-xxxl

fix: DEBUG-516 journal-save-button is 100% occluded by the keyboard at the largest non-accessibility Dynamic Type step
…efect

The comment asserted main's workflows were stale with a specific list (retired
workflows, older NODE_VERSION, differing job-list). The v1.2.0 release synced
them, making that false β€” but writing "they now match" would rot just as fast:
release.yml diverged again within hours of the release, via PR #425.

Restated as the structural rule instead. main carries whatever the last release
shipped; every workflow change merged to development after that is absent there;
the gap re-opens after every release. Names the check to run rather than a
point-in-time state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015eW4ZheBj6wVHWekhKujRz
chore: INFRA-459 main-branch-health SCOPE comment stated drift as a defect
The main worktree is retired β€” main is only ever a ref in this repo, moved by
the release and checked out by nothing. Its HEAD reflog held a single entry, its
node_modules was six weeks stale, and app/ios had never been built.

So 'cd ~/dev/being/main' names a path that does not exist, and the obvious
substitute β€” git checkout main in the development worktree β€” would move that
worktree off development, which /b-release and /b-close assume.

Cuts a throwaway worktree off origin/main instead (satisfying strict: true by
construction), spells out the env symlinks that worktree add does not create,
and closes with its removal.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
docs: hotfix cuts a worktree off origin/main
The completed Daily Practice card applied `opacity: 0.5` to the whole
Pressable. React Native composites the entire subtree, so it scaled the
contrast of every descendant at once β€” including `semantic.text.secondary`
on the description and the `gray[400]` border chosen three lines above
precisely to clear 3:1. Both fell under AA in the state a healthy daily
user sees every single day after they practise.

Opacity is not a colour token and cannot be contrast-audited, so completion
is now expressed structurally: the filled full-width bar is replaced by a
quiet `βœ“ Done today` status line in `themeColors.primary`. That also
retires the "Complete" label, which in a high-contrast filled bar parsed as
an imperative telling the reader to complete what they had just completed.

The card remains the tap target and its accessibilityLabel/Hint still
announce completion and offer the restart, so no touch target is lost.
Press feedback (`opacity: 0.9`) is unchanged β€” this is scoped to the
`isCompleted` branch.

The completed state was previously unreachable from any test: the store
mock hardcoded `isCheckInCompletedToday: () => false`. It is now mutable,
mirroring the existing `mockFlags` pattern, with two controls that stay
green across the change so a red cannot be confused with a dead harness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBAW7cM5qUVzcV3c6dAoYU
…eached

DEBUG-465 ruled, in this file, that "a scroll in front of `continue-button`
is now REQUIRED and is not the same act" as the scroll in front of
`daily-loop-support-line`, which stays banned forever. Only the Sphere
Sovereignty site ever got one. Quick beat 1 and deep beat 1 stayed bare, so a
Continue below the fold surfaced as a plain `element not found` β€”
indistinguishable from layout drift on a flow whose reds are usually layout.

Adds a boundary `scrollUntilVisible` (direction DOWN, no centerElement) at
both, making such a failure attributable. A detector, not a fix: nothing is red
today and `continueWrap`'s CRISIS_FAB_CLEARANCE is already unconditional.

The item's two stated mechanisms are both refuted, and the comments say so
rather than repeating them:

  - The pinned support line cannot collide with these taps.
    `showsSupportLine()` is false on Aware Presence, the beat both taps are
    dispatched from; `supportLine: true` occurs only on RadicalAcceptance
    StepConfigs.
  - The `crisis-button-root` overlap does not reproduce here either. The 24pt
    measurement at DailyLoopStepScreen.tsx derives from the support bar's 58pt
    viewport reduction and scopes itself to beats where showsSupportLine() is
    true. Without the bar the CTA sits below the FAB band, not inside it.

So no assertion claiming a crisis false positive was added at either site: it
could not go red, and this file already records that continue-button carries no
reachability contract.

No centerElement, deliberately. It is load-bearing at Sphere Sovereignty, where
it proves a real scroll happened and keeps the tap off the co-located support
line β€” untouched there. Here there is no co-located affordance, and centring
would re-position mid-content and reintroduce the DEBUG-477 swallowed tap
(probe F). As written both DEBUG-477 arms are clear: already-visible means zero
swipes; below the fold means a DOWN scroll to the last ScrollView child, which
terminates at the content boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
The item's premise was true when filed and is false now. FEAT-457 shipped
guidance-suppressed-handoff.yaml (bb70cb8 / cc26461, PR #407) alongside the
consumers whose absence had blocked this item, so the SUPPRESSED branch β€” the
protective one, where a distressed reader must receive zero philosophy β€” is
already pinned end to end. Authoring a second GAD-7-seeded flow against it
would have produced two independently-seeded completions asserting one
contract, which drift apart silently.

What had no device-level pin at all is the POSITIVE side: that a reader the
gate does not suppress actually receives Tier 0 and Tier 1, and that Tier 2/3
stay capped unless allowTier2Plus is true. That is what this flow adds. The
failure it catches is over-disclosure, not crisis silence.

It costs zero assessment taps. `gentle` is the verdict for MISSING data, not
only for mid-band scores β€” guidanceGate.ts: "absence is never evidence of
safety, so it cannot yield full ... gentle is the only defensible answer."
The sibling records the same fact from the other side at its re-launch: "NO
clearState HERE ... clearing would make the gate answer `gentle`."

Two design points the flow's header derives rather than asserts:

  - Positive assertions come FIRST. useGuidanceGate reports `pending` while
    the encrypted store hydrates, and every assertNotVisible would pass
    vacuously in that state and on `guidance-error`. Asserting guidance-content
    and guidance-tier0 first is what makes the absences mean something.
  - No scrolling. Tier 2/3 render inside the guidance-content ScrollView, and
    per DEBUG-465 XCUITest retains elements clipped by a ScrollView, so
    assertNotVisible at offset 0 is a real claim about whether the tier
    RENDERED. A scroll would weaken it to absence-at-an-offset.

Declares 375x667 rather than `any`: the contract is viewport-independent but
the reach is not, and the only viewport on which tapping home-guidance-entry is
proven is the one the sibling certifies.

Count tripwire bumped 11 -> 12 in the same commit, as its own comment requires.

ACs 4, 5 and 6 are .claude/-only (the /b-close Step 2.5.3 mapping and the
CLAUDE.md flow count) and are gitignored on development, so they land as a
separate _bare commit. AC2's boundary cases need both axes on record β€” 16 real
UI taps β€” and are tracked in INFRA-529.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
Narrowed by founder decision to date range only. Search is FEAT-530 and tags
are dropped; the scope-down comment on the item records both.

The filter reads createdAt off JournalEntryMeta, which listEntryMetadata()
already returns from one decrypt of the single index record β€” zero per-entry
getEntry calls, zero new storage, no new screen and no new store. Selection
lives in component state for the same reason plaintext does: a persisted filter
is a record of which spans the reader returns to.

Full-text search cannot be built this way, which is why it is a separate item.
EncryptionService disables derived-key caching because each record carries its
own salt, so every getEntry is a fresh 100k-iteration PBKDF2 and search is O(N)
derivations with no amortisation.

Preset names (last7/last30/last90/all) are reused verbatim from
features/data-export's ExportRangePreset so the two surfaces agree. The code is
deliberately NOT imported: journalAnalyticsBoundary.contract.test.ts walks the
journal feature directory, so a shared module outside it would be invisible to
the egress checks that guard this feature.

Ordering is inherited, never recomputed. listEntryMetadata() returns
newest-first and Array.filter preserves order, so there is no sort call β€” and a
sort is exactly where a relevance ordering would enter. Recurrence is perceived
across time, so reverse-chronological is the axis the pattern lives on.

An empty RESULT is not an empty RECORD. journal-history-empty says reflections
"will appear here", which is false for a reader who has written some and
narrowed past them, so the filtered case gets its own copy reporting the
control's effect and nothing about the reader. The filter stays mounted above
it, or the only way out of an empty result would be to leave the screen.

Buttons only β€” no TextInput, so the keyboard still never rises on this screen
and CrisisKeyboardAccessory is not pulled onto a surface that has never needed
it. No RN <Modal>: crisis-zero-988-windows.test.tsx forbids one here
mechanically. Rows stay identical under every preset.

Also closes a pre-existing hole this feature's own AC depends on:
journalAnalyticsBoundary.contract.test.ts pinned PostHog, Supabase and raw
network egress and contained ZERO Sentry references, so
Sentry.addBreadcrumb({ message: `filter applied, ${n} results` }) passed every
check from inside features/journal. Sentry is now scanned with an empty
allow-list, captureException included β€” an exception thrown while handling
entry text can carry that text.

Tests: filter arithmetic with inclusive millisecond boundaries; the screen's
empty-result, escapability and decrypt-count behaviour; and a source-shape
examiner-boundary guard carrying all three DEBUG-390 rails, which these modules
need because they name the forbidden mechanisms in prose to warn readers off
them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYpWHMMLhowjbiGR3owEY9
…ilter

feat: FEAT-288 Voice journal β€” Slice C: date-range review (on-device, zero egress)
MP2EZ and others added 27 commits September 7, 2026 17:26
`crisisButtonGeometry.ts` was created by DEBUG-406 because of `HapticsOptInPrompt`
and `ResumeSessionModal`, and never reached them. Both re-derived
`104 + TOUCH_TARGETS.minimum + 12 + spacing[16]` as a literal for the whole life of
the module, while the module's header named them as consumers.

Two oracles, both answering "covered", both wrong in the same direction: a reader
found the header sentence, and INFRA-531's import detector β€” anchored on the
`@/features/crisis/constants/` specifier β€” found no import. So moving the FAB's
`bottom` would update `CRISIS_BUTTON_BOTTOM_OFFSET`, keep the geometry test green,
and leave both overlays under-reserved. At `zIndex: 9999` the button wins the
overlap, making that a crisis FALSE POSITIVE (the DEBUG-547 shape) β€” aggravated on
`HapticsOptInPrompt`, whose mis-tap lands on the DECLINE side of a once-ever
question, and on `ResumeSessionModal`, which auto-triggers on stale-session detect.

- Both files import `CRISIS_BUTTON_RESERVED_BAND` via the ALIAS specifier. A
  relative import is functionally identical and matches nothing in
  `I531_IMPORT_RE`, so it would reproduce the invisibility this item removes.
- Local declarations deleted, not re-bound. The arithmetic recital in each doc
  block is deleted (it was the copy); the consequence prose is kept verbatim and
  moved to the `paddingBottom` site β€” `crisisButtonGeometry.ts:28` cites
  HapticsOptInPrompt's "an overlap is unrecoverable" by name.
- Header corrected in the same commit: it asserted a consumption that did not
  exist, and that assertion is what stopped a reader checking.

Tests. `ResumeSessionModal.test.tsx`'s loose `>= 156` floor becomes equality
against the imported constant β€” the floor passed while the file held its own
literal. `haptic-cues-accessibility.test.tsx` gains the band assertion it never
had, which matters because the prompt is flag-dark in the gate build
(`practice_haptics:false` in eas.json's e2e-sim profile), so no Maestro flow can
render it and jest is its only oracle. A source-shape pin in the geometry suite
enforces the alias form and the absence of a local declaration; it is the only
CI-visible control, since the Protected Paths rows and the Phase 2.5 arm live in
`.claude/`, gitignored on `development`.

Crisis planning pass ruled GATE over EXEMPT (a paddingBottom sized from the FAB's
own geometry is the placement decision `i531_exempt_reason()` excludes) and
FILE-level over a directory clause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJmk5ePJDhGMWcSs2vTGrd
…umbing

Slice C, PARTIAL β€” the Maestro flow is written but UNVERIFIED. See below.

Verified and complete:
- __tests__/safety/perfHudGate.config.test.ts (13 tests). Pins that
  EXPO_PUBLIC_PERF_HUD is enabled in NO eas.json profile (iterating every
  profile, so one added later fails by default rather than being silently
  exempt), that the schema defaults it 'false' so omission cannot enable
  the HUD, and that the probe is mounted exactly once and only behind the
  flag. Source assertions strip comments first and each matcher is proved
  to fire and to reject a known-bad literal (DEBUG-390).
- e2e-safety.sh: device-only tag is now a union, (safety|perf)-device-only.
  Matching only the safety string would have cleared DEVICE_ONLY for the
  perf flow, sent it to a simulator, and measured the host Mac's GPU.
  No-op for existing flows: none carries the new tag.
- e2e-safety.sh: --apple-team-id on device runs, read from app.json's
  expo.ios.appleTeamId. Without it maestro cannot build its XCUITest driver
  and dies with no report, which reads as a flow regression.
- e2e-safety.sh: -e forwarding via E2E_MAESTRO_ENV. Empty when unset, so
  every existing invocation is byte-identical. Needed because the AC
  requires the threshold be a required parameter with NO default.
- .maestro/breathing-fps-budget.yaml + npm run e2e:perf:breathing-fps.

NOT verified, and not claimed: the flow has never executed. maestro 2.6.0
cannot build its iOS driver for a connected iPhone β€” its own extracted
project is missing MaestroDriverLib/Info.plist. This is upstream, not
configuration: it survives a clean ~/.maestro/maestro-iphoneos-driver-build
and reproduces identically on crisis-988-dial.

That last point is the bigger finding. crisis-988-dial is the only
safety-device-only flow and it pins the crisis dial path; it fails in 8s
with NO_REPORT on this toolchain. The documented device-only safety
procedure cannot currently run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found while running this item's own device flow. maestro 2.6.0 cannot
build its XCUITest driver for a connected iPhone, so the only
safety-device-only flow fails in ~8s with NO_REPORT before any step runs.
Annotated in the flow itself so a red is not misread as a dial-path
regression. The config half stays pinned and CI-gated by
lsApplicationQueriesSchemes.config.test.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tro version

No released Maestro can execute a flow on a physical iPhone, so both
`safety-device-only` flows β€” `crisis-988-dial` and `crisis-keyboard-accessory` β€”
have been silently unrunnable. The hazard is a procedure that reads as coverage
while being dead, so this records the loss rather than papering over it.

Measured 2026-09-07 on iPhone 16e / iOS 26.6 / Xcode 26.0.1 / team KN6FDLG98K
across 2.0.0, 2.1.0, 2.2.0, 2.4.0, 2.5.1, 2.6.0, 2.6.1, 2.7.0, 2.8.0, 2.9.0 and
2.10.0 β€” two failure modes, no survivor:

  >= 2.2.0  the shipped driver Xcode project declares a MaestroDriverLib target
            (47 pbxproj refs) and the MaestroDriverLib/ directory is shipped in
            ZERO releases, while five UITests sources import it. Build dies in
            ~8s with no JUnit report. 2.10.0 is latest, so upgrading cannot fix.
  <= 2.1.0  predates that target and BUILDS, but the XCUITest runner never
            becomes ready within a 300s MAESTRO_DRIVER_STARTUP_TIMEOUT.

The hardware is healthy β€” the installed runner launches by hand via devicectl.
Simulator flows are unaffected: they use a prebuilt driver and never compile,
which is why 14 sim flows are green while these two cannot start.

Changes:
- e2e-safety.sh refuses the device path up front with exit 5 rather than letting
  it die at ~8s with NO_REPORT, which is indistinguishable from a dial-path
  regression. E2E_FORCE_DEVICE_ATTEMPT=1 re-attempts, for testing the exit
  condition.
- Pin the Maestro version (app/package.json -> maestro.pinnedVersion), enforced
  fail-closed. Nothing read `maestro --version` before, so `brew upgrade` moved
  the toolchain with no diff and no failing check and the gate silently
  re-baselined itself.
- Record the unavailability in both flow headers, docs/testing/e2e-maestro.md,
  and (separately, on _bare) CLAUDE.md β€” with crisis's ruling that the two flows
  are asymmetric: the dial is unrecoverable (sim canOpenURL is unconditionally
  false), while the accessory's reachability half is sim-runnable today and is
  recorded as NOT YET MIGRATED, not as unavailable.
- deviceOnlyFlowsUnavailable.test.ts pins that the record stays present, bounded
  and true, incl. that the safety-device-only set is exactly those two so a third
  cannot inherit the gap silently. Mutation-verified: both mechanisms go red
  independently.

Per the crisis ruling, compensating follow-ups are filed: DEBUG-590 (move the
accessory reachability half to the sim suite), INFRA-591 (attended device
checklist, release-gated), INFRA-592 (assert the generated Info.plist keeps
tel/sms after plugin composition).

Verified: test:safety 630/630, typecheck clean, lint:baseline no new errors,
gate-tooling script suites green (exit-alphabet 18/18 incl. two new exit-5 pins).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NfQXVXhWB2S2pAqHkJUHVQ
…sedes it

DEBUG-589 concluded that device flows are dead on every Maestro version
2.0.0-2.10.0, not merely broken on 2.6.0, and PR #474 annotates the flow
properly with an up-front exit-5 refusal. My placeholder said "pending
DEBUG-589", which would have been stale the moment #474 landed β€” and it
was the only file conflicting with that branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught what my local run missed. Three suites spawn e2e-safety.sh end-to-end;
I patched two and never ran the third, having excluded e2e-sim-build.test.js from
the local sweep as slow. Both new pre-flights hit it:

- Its project fixture writes a package.json with no `maestro.pinnedVersion`, so
  the version check refused (exit 2) and pre-empted every arm with the wrong
  message. `maestro` is not a CNG-projected key, so the fingerprint cases are
  undisturbed.
- Its `maestro` stub did not answer `--version`. It now does, BEFORE the trace
  append, so the pre-flight's call cannot appear in a trace that other specs
  count invocations from.
- The 14 INFRA-424 device-resolution specs reached the exit-5 refusal instead of
  device resolution. runSafety() now sets E2E_FORCE_DEVICE_ATTEMPT=1 for
  device-only selections, so they keep testing what they were written to test.

That auto-set would otherwise hide the refusal from this file entirely, and a
later edit removing it would read as a no-op β€” so two specs clear the hatch and
pin the real default (exit 5, zero flows run, blames the toolchain not the
hardware).

Test-only change: `__tests__/` is excluded from Phase 2.5's classification
because Maestro drives the running app, so the gate verdict on 4956174
(crisis-button-reachability PASS, CERTIFIED 375x667) still stands.

Verified with CI's own invocation: npm run test:scripts -- --ci
--testTimeout=20000 β†’ Test Suites: 30 passed, Tests: 691 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NfQXVXhWB2S2pAqHkJUHVQ
…uild

fix: DEBUG-589 record the device gate as unavailable and pin the Maestro version
… calibration

Re-scope after DEBUG-589: no Maestro version can run a flow on a physical
iPhone, so the device-only flow asserting the HUD is not blocked, it is
unavailable. INFRA-373 therefore delivers a manual instrument and no gate.

Runbook Β§5a now carries what was built, the measured calibration (iPhone
16e / iOS 26.6 / 60Hz: 599 frames, nominal 16.66, zero drops), the manual
procedure, and the two control runs still owed before any threshold is set
β€” which is the durable home the AC required, instead of a commit message.

Also corrects two claims Β§5a made:
- Normalising does NOT need a native call. The nominal comes from the modal
  interval; measured 16.66 with no UIScreen.maximumFramesPerSecond binding.
- Take the mode, not the minimum. Measured min 16.42 against a true nominal
  of 16.67 β€” frames land early, so the minimum is biased low and inflates
  the dropped-frame ratio.

Says "instrument, not gate" in as many words, because this section exists
downstream of MAINT-307 correcting a claim that Maestro enforced budgets it
never asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rt-swap

fix: DEBUG-586 import the crisis band instead of re-deriving it
Four readings on iPhone 16e / iOS 26.6 / 60Hz Release, all zero dropped
frames, nominal 16.67 from the modal bin every time.

Control (a) β€” the JS-thread haptic cue chain has no measurable effect on
UI-thread frame delivery. Rows 1/1b/2 ran with the scheduler live and row 3
with the pipeline off. Both gates are required for it to run at all
(tactileEnabled = flagOn && practiceHaptics === true, and this screen passes
no announce), which is why the persisted opt-in state mattered. The null is
the architecturally expected result and is the first on-device evidence here
that the worklet/JS-thread separation holds.

Control (b) β€” the probe's own per-frame cost is under one frame interval.
Recorded as an upper bound, not a measurement: both arms run the probe, and
a probe-off arm yields no numbers by definition.

Also records run-to-run variance at Β±1 frame (rows 1 and 1b, same binary,
same conditions), which any future threshold has to clear to mean anything.

Explicitly does NOT set a threshold. Every run read exactly zero, which
supports "ratio must be 0" β€” brittle β€” or nothing, and DEBUG-589 leaves no
gate for a threshold to live in. Inventing one here would be the
permanently-green no-op this item exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uin selection

Both Seneca *Letters* records declared `work: "Letters from a Stoic"` over
`translation: "Richard Mott Gummere"`. That title is Robin Campbell's 1969
Penguin Classics selection, which is in copyright; Gummere's Loeb text is
*Ad Lucilium Epistulae Morales*. So the metadata shelved a public-domain
Loeb text under a copyrighted Penguin name β€” the DEBUG-352 / FEAT-567 /
DEBUG-582 defect class one layer up, in the field a reader would use to
find the source.

Renamed to `Moral Letters to Lucilius`: the title DEBUG-582's pinned
Wikisource transcription itself runs under, so the type key traces to the
pin. `Epistulae Morales` was rejected as a truncation matching no title
page in the pin. Title-cased in the union because it names the work;
the suite docblock keeps sentence-case because it quotes the source page
verbatim.

Citations are deliberately UNCHANGED. `Letters 13.4` / `Letters 107.11`
are standard scholarly short form, attach no edition identity, and are the
only reader-facing strings here β€” `work` is metadata that no surface
renders. Lengthening them would newly commit this defect in the one field
a reader reads, and would desync `practiceQuotes.test.ts:323`, whose
`Letters \d+(\.\d+)?` format gates the CORPUS lookup.

README 152 and 413 are corrected in the same pass. 413 was the worst
instance in the repo: it sat inside the sentence asserting "public-domain
translations, the only renderings shipped in the app" while naming the
copyrighted edition. 152 additionally dropped *Discourses*, which nothing
in the repo ships from. `operations/resources.md` is untouched by design β€”
it recommends the Penguin edition to a reader, which the existing suites
deliberately partition from claiming it as our source.

Also corrects the public-domain rationale DEBUG-582 landed: life+70 is not
the "weaker footing" for a pre-1978 published work, it is the wrong theory
outright. The US term for works published 1923-1977 runs from publication,
never the author's life, so Gummere's 1955 death is irrelevant. Pre-1929
US publication is the sole basis.

The new pin reads module assets HERE rather than in
`moduleClassicalQuotes.test.ts`, which sits under
`src/features/learn/__tests__/`, matches no CI `--testPathPattern`, and is
listed in `scripts/ci-uncovered-tests.json` as ungated β€” a pin there runs
on nobody's PR. Asserted on the `work` field, never raw file text, so it
cannot collide with a note that names the banned title to warn against it.

Balance ratchet re-derived rather than assumed: across all five files the
only delta is the byWork KEY; every count and total is byte-identical, so
passages-4 stays 2/2 against floor(4/2).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJmk5ePJDhGMWcSs2vTGrd
…-sheet blocker

INFRA-571 registered three presenter call sites in PRESENTER_ALLOWLIST at two
different evidentiary standards. This settles the expo-sharing one and records
why the StoreKit/Play Billing one cannot be settled here.

MEASURED (2026-09-07, Release build, iPhone SE 3rd gen / iOS 18.6, 375x667):
with the share sheet up, `maestro hierarchy` carries ZERO app-owned nodes β€” an
absent app tree, not a covered FAB, leaving a full-bleed PopoverDismissRegion
and the sheet's out-of-process ShareSheet.RemoteContainerView. Presence was not
the evidence; hit-testability was. A matched-pair coordinate tap inside the
FAB's measured bounds reached CrisisResources with the sheet DOWN and did not
with it UP β€” same route, same point, same run, twice. That pair is also the
falsification demonstration. The inferred reading UNDERSTATED the occlusion.

The exception STANDS and does not convert: UIActivityViewController is OS-owned
and out-of-process, so no RN element, zIndex or z-order change reaches above it.
It is bounded only by the user dismissing the sheet.

IAPService stays NOT MEASURED, with its blockers now enumerated rather than left
to be re-derived: mockMode = __DEV__ makes the call unreachable in any Debug
build, no .storekit config exists so simulator StoreKit resolves no products,
and Android needs a Play-signed build plus a licence tester against an Android
e2e harness that does not exist.

Also fixes the assertion that was supposed to police this boundary: it pinned
the measured entry with /MEASURED/, which is a substring of "NOT MEASURED", so
it passed on prose asserting the opposite. Measured entries now match
/MEASURED ON (DEVICE|SIMULATOR)/ and are asserted not to carry the negation.

ExportDataScreen's header said the route inherits "the sibling
CollapsibleCrisisButton overlay". That mount was removed in MAINT-290; a probe
written from it would assert an unmounted testID and report an occlusion it
never tested.

The measurement flow is committed under its own tag so the suite cannot select
it. It pins a DEBT state β€” its load-bearing assertion is that the affordance is
unreachable β€” so it stays green after a fix and must be deleted, not repaired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVJWaGwB6q65r94ysb2Trs
Caught by CI's Script guard tests, which no precommit pattern runs.

The team-id lookup hard-failed with exit 2 when app.json was unreadable.
That is too aggressive: an unresolvable team id is recoverable β€” maestro
reports it itself β€” and exit 2 means "no verdict", which is strictly worse
than letting the driver build fail with its own message. It also broke the
proceed-anyway contract the INFRA-424 device tests encode, since their
sandbox stages the sourced helpers but not app.json.

Now warns and omits the flag. 114/114 script guard tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…measure-inferred-presenter-occlusions

FEAT-570 deleted the DEBUG-533 allowlist entry along with the call it examined,
so the measured/inferred partition this branch introduces now names
ExportDataScreen as the measured side and IAPService as the inferred one. Two
cross-references in this branch's ruling pointed at that deleted entry and are
repaired: the structural-conclusion citation, and INFRA-571's reachability
comparison against the Sentry widget, which no longer exists β€” the reachability
claim now stands on its own facts.

Verified the measurement still holds: every file on the measured path
(ExportDataScreen, RootCrisisButton, crisisButtonGeometry, PrivacyDataScreen,
rootOverlaySlot) is byte-identical between the measured tree and this merge base.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVJWaGwB6q65r94ysb2Trs
chore: INFRA-373 device-measured breathing frame probe (instrument, not gate)
…oeb-title

fix: DEBUG-585 name the Loeb edition Gummere translated, not the Penguin selection
…senter-occlusions

fix: DEBUG-577 measure the share-sheet occlusion, rule the purchase sheet unmeasurable
Bump version across all four INFRA-141 sources:
- app/package.json
- app/app.json (Expo manifest + iOS buildNumber + Android versionCode)
- .config/.env.production (untracked, edited in place)
- .config/.env.development (untracked, edited in place)

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
A new HIGH advisory (CVSS 7.5, CWE-400 ReDoS-class: maxTotalMergeKeys does
not limit CPU use for empty merge sources) published against js-yaml
>=3.0.0 <3.15.2 and >=4.0.0 <4.3.2. It landed after development's last
green run and fails `Security + compliance` on every open PR, including
this release's bump PR β€” the gate is severity-based and this is un-allowlisted.

Targeted fix per the standing npm-audit rule, never `npm audit fix`:
the existing pins were already at 3.15.1 / 4.3.1, so the fixed releases
are in-range patch bumps of the same pins. Bumped all three keys in
`overrides` and mirrored them in `resolutions`:

  js-yaml@^3.13.1  3.15.1 -> 3.15.2
  js-yaml@^4.1.0   4.3.1  -> 4.3.2
  js-yaml@^4.1.1   4.3.1  -> 4.3.2

Not allowlisted, because a real fix exists and the consumers tolerate it β€”
all three are build-time only (@react-native/jest-preset -> babel-jest ->
babel-plugin-istanbul, eslint -> @eslint/eslintrc, and expo -> @expo/cli ->
@expo/xcpretty). Nothing here reaches the app bundle.

Lockfile movement is confined to js-yaml and the argparse it pulls: the
3.x nested copy bumps in place, and the two nested 4.3.1 copies dedupe
into one hoisted 4.3.2. No expo or react-native package moved, so this
carries no native-surface change (Phase 2.8 clean).

Verified: `npx audit-ci --config .audit-ci.json` -> "Passed npm security
audit", exit 0 (high 8 -> 7, the remaining 7 all allowlisted);
`npx patch-package` -> expo-modules-jsi@56.0.12 βœ”.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
chore(release): v1.3.0 version bump + unblock the npm-audit gate
Records `main`'s history on `development` with ZERO content change. The
resolved tree is byte-identical to `origin/development` β€” verified with
`git diff origin/development` (empty).

Why it was needed. The v1.3.0 release PR opened CONFLICTING against `main`
even though Phase 2.4 of /b-release found no divergence, and both checks
were correct:

- `main` carries hotfix PR #435 (`d1e6e840`, DEBUG-534 legal copy), which
  was backported to `development` by cherry-pick as `4b2119d7`, per the
  Hotfix Process. Cherry-pick rewrites the SHA, so Phase 2.4's
  `--cherry-pick --right-only` patch-id check correctly cleared it: `main`
  has no unique CONTENT.
- But `development` then moved PAST that content. DEBUG-545 took
  privacy-policy.md 1.10 -> 1.11 and added three DPIA changelog rows. Git
  sees two independent edits to the same regions with no common ancestor
  for them, so a textual conflict remains even though one side is a strict
  superset of the other.

Both conflicts resolved to `development`, which is the superset in each:
  docs/legal/privacy-policy.md            main 1.10 vs dev 1.11 -> dev
  docs/legal/dpia-sensitive-wellness-data.md  main empty vs dev rows
                                          2.10/2.11/2.12 -> dev

`docs/architecture/analytics-architecture.md` auto-merged and is likewise
unchanged from `development`.

Note for the Hotfix Process: CLAUDE.md states that after a cherry-pick
backport "git handles identical content gracefully on the next release
PR." That holds only while `development` does not subsequently edit the
same lines. It did here, and the conflict surfaced at release time rather
than at backport time.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CkZagJp7dzojvrjY7uYwkh
chore: back-merge main into development to clear the release conflict
@MP2EZ
MP2EZ merged commit e7b97d7 into main Sep 9, 2026
22 checks passed
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.

1 participant