Skip to content

feat(mobile): harden bootstrap, routing, and release gates (w6b) - #5421

Merged
iscekic merged 114 commits into
mainfrom
audit-w6b-bootstrap-release-ff5f
Aug 24, 2026
Merged

feat(mobile): harden bootstrap, routing, and release gates (w6b)#5421
iscekic merged 114 commits into
mainfrom
audit-w6b-bootstrap-release-ff5f

Conversation

@iscekic

@iscekic iscekic commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

The Home screen now shows an Explore area with three shortcuts: Code Reviewer, Security Agent, and PR Review. The area stays visible when the session list is loading, empty, filled, or failed to load.

The Profile screen now paints first and loads account and organization details only after the screen transition settles, showing placeholders while it waits.

Opening a link with a broken review, session, or organization identifier now shows a not-found screen with a back action instead of a blank or broken screen.


The production host allowlist is now complete. It holds all seven production hosts from the committed environment file, including the three kiloapps.io endpoints, and a unit test reads that file and asserts every URL value passes the scheme and host checks, so a missing host fails before any build. A production build still fails on a missing required value, a wrong scheme, an off-allowlist host, or a missing Sentry source-map token.

Files
  • apps/mobile/src/lib/url-contract.js — new; holds URL_SCHEMES, PRODUCTION_HOSTS, assertUrlScheme, and assertProductionHost; the allowlist now lists all seven production hosts.
  • apps/mobile/app.config.ts — applies the scheme and host checks; makes missing env fatal in production; adds the Sentry token gate; bakes extra.isProductionBuild.
  • apps/mobile/src/lib/config.ts — applies the scheme check at runtime and the production host check when extra.isProductionBuild is true.

The root layout now derives its bootstrap routing and its render gating from one pure resolver instead of two hand-kept copies, so the routing effect and the visibility flags can no longer disagree. The resolver returns a closed union of routing tags plus the error, redirect, and hidden flags, and the routing effect switches over the tag. The old inline if-chain and the old inline gating derivations are deleted; behavior stays the same.

Files
  • apps/mobile/src/lib/bootstrap-decision.ts — new; exports the input, decision, and tag types and the pure resolver.
  • apps/mobile/src/app/_layout.tsx — calls the resolver in the routing effect and the render gate; replaces the inline if-chain with a switch.

The release workflow pins EAS CLI to 21.8.0 and splits the old build-and-submit step into a preflight job, a build job, an artifact download, an inspection step, and separate iOS and Android submissions. The preflight fails closed when the EAS production environment misses a required value or the config contract fails. The build no longer uses --auto-submit; submission runs from downloaded artifacts after inspection.

Files
  • .github/workflows/kilo-app-release.yml — pins the CLI, adds the preflight job, splits build and submit, adds download and inspection steps.
  • apps/mobile/eas.json — pins cli.version to 21.8.0.
  • apps/mobile/package.json — pins every eas-cli script to 21.8.0 and adds the assert:config script.

A new preflight script runs the fully evaluated Expo config and fails when the bundle id, package, scheme, associated domain, blocked permissions, Sentry plugin, or any required extra value violates the contract. The preflight invokes it inside the EAS production environment, so config drift fails before any build.

Files
  • apps/mobile/scripts/assert-expo-config.mjs — new; runs expo config --json and checks the contract values.

A new inspection script validates the EAS build result and the signed IPA and AAB before submission, and submission stops when an artifact violates a bundle, permission, endpoint, or debug-symbol contract. The IPA is checked for its bundle id, privacy manifest, usage descriptions, and SKAdNetwork endpoints; the AAB is checked for its package, blocked permissions, cleartext traffic, intent-filter host, and debug symbols.

Files
  • scripts/inspect-mobile-artifacts.mjs — new; validates build.json, inspects the IPA and AAB, and checks debug symbols.

The home screen gains an Explore section with Code Reviewer, Security Agent, and PR Review rows, and the PR Review row stays behind the existing PR Review feature flag. The section renders in the empty, populated, and error states of the session list, so the shortcuts remain available when sessions fail to load.

Files
  • apps/mobile/src/components/home/product-choices.tsx — new; three rows, PR Review gated by the flag.
  • apps/mobile/src/components/home/home-screen.tsx — renders ProductChoices after the sessions/promo branch.

The profile screen defers its two mount-time queries until the navigation transition settles, using a new useAfterInteractions hook, so the screen paints before the account and organization fetches start. The skeleton, stale, and error states stay, and the linked-accounts section now shows its skeleton while the interactions are pending.

Files
  • apps/mobile/src/lib/hooks/use-after-interactions.ts — new; returns false on mount, then true after InteractionManager.runAfterInteractions.
  • apps/mobile/src/components/profile-screen.tsx — gates both queries on the hook and keeps the skeleton and error states.

Six routes that read a required path parameter now parse it with parseParam and render InvalidRouteState with a bounded back target when the parameter is missing or malformed. Optional parameters keep their existing first-element unwrapping, and the session route no longer fires its query for a null session id.

Files
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/index.tsx — parses scope; renders InvalidRouteState.
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/manual-review.tsx — parses scope; renders InvalidRouteState.
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/[id].tsx — parses scope and id; back target depends on scope.
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/reviews/index.tsx — parses scope; renders InvalidRouteState.
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/organization/member-limit.tsx — parses memberId; renders InvalidRouteState.
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx — parses session-id; disables the query and renders InvalidRouteState when null.

Tests: 13 test files added or updated.
Generated: none.


Verification

Two cases ran on iOS across two verification rounds.

Case What it proves Platform Result
EV-03 cold start to interactive Home A relaunch from force-quit lands on the interactive Home; no local sink observes the startup event in dev builds. iOS passed
EV-08 invalid route boundary A malformed deep link renders a bounded not-found state on the device; the route tests prove the InvalidRouteState back target. iOS passed

No round reproduced a defect on the unfixed build.
Recording: ev03-coldstart.mp4 shows the EV-03 cold launch from force-quit to interactive Home.

Visual Changes

Home screen. The user now sees an Explore section on Home with rows for Code Reviewer, Security Agent, and PR Review. In the picture, the EXPLORE header and the three rows sit below the Kilo Agents card and above the tab bar.

01-open-app.png

Invalid-route screen. The user now gets a bounded not-found state with a back action when a route param is malformed. No picture shows this change. The single candidate shows the session not-found state after a malformed deep link; no deep link yields a malformed param on the device, so the picture fails to show the new InvalidRouteState with Page not found and Go back. The co-located route tests prove that state instead.

Reviewer Notes

Human steps:

  1. Confirm the EAS production environment URL values match the committed apps/mobile/.env hosts before the first real production build; the release preflight fails closed on any mismatch. (after merge)
  2. Store SENTRY_AUTH_TOKEN in the EAS production environment with non-secret visibility (plain or sensitive). (after merge)
  3. Set EXPO_PUBLIC_SENTRY_ENVIRONMENT=production in the EAS production environment; the release preflight asserts it. The EAS preview environment no longer needs EXPO_PUBLIC_SENTRY_ENVIRONMENT=preview to avoid a crash, because the runtime host check now keys off the baked build intent. (after merge)
  4. Watch the first real release run: the signed IPA may lack PrivacyInfo.xcprivacy or the AAB may lack debug-symbols metadata; the follow-up fix is named in the plan. (after merge)
  5. The signed IPA's binary Info.plist is parsed with python3 plistlib; the release runner must provide python3. (after merge)

Notes: E2E: the follow-up commit 13f4d4b adds three production hosts to the build-time allowlist and a unit test; the host check only runs in production builds, so it has no runtime behavior to verify. The existing iOS verification still holds.

iscekic added 30 commits August 21, 2026 03:41
Export SECURITY_COMMAND_TYPES as the shared tuple and derive
SecurityCommandType from it. Add an app-shared invalidation-scope test
and an apps/web drift test asserting the db union matches the shared
tuple. Type the web create-call literals and the SecurityAgentAdmissionAction
redeclaration with the shared type.
Add an additive security_lifecycle variant to pushDataSchema with an
eight-value event enum mapped 1:1 to SecurityAuditLogAction. Route the
variant to the existing security channel and add lifecycle lock-screen
copy in both exhaustive presentation switches.
… review

Extend SECURITY_INTENTS to the four command intents with an authority-linked
SECURITY_INTENT_FOR_COMMAND_TYPE map. Add the code_review_settled terminal
event with a privacy-minimal schema and add the code_review operation
ledger domain.
Add an Explore section with Code Reviewer, Security Agent, and PR Review
rows so an empty or erroring Home still offers an outcome-led first
action. Scope resolves as organizationId or the personal security scope;
PR Review stays feature-flag gated.
Add getCommandStatuses on the personal and org routers with a
min(1).max(100) uuid-array input schema. Batch-fetch owner-scoped
commands, omit unknown or foreign ids, and settle terminal commands
through the existing ledger settle helper. Keep getCommandStatus for
older mobile clients.
Require ORGANIZATION_BILLING_ROLES when an org already has a GitLab
integration, on both the OAuth connect and callback paths; first-time
connect keeps member access. Add a role matrix over every
organization-security-agent procedure and map access denials to
permission-specific error codes instead of connection errors.
Extend the notification preferences getter with a capabilities map for
the seven category keys, computing availability from organization
membership, enabled Security config, and KiloClaw instances.
…ions

Narrow the security findings list, code reviews list, and organization list responses so heavy content is fetched only by detail procedures. The findings list nulls raw_data after decoration; the code reviews list drops council_result, manual_config, and previous_summary_body; the organization list selects explicit columns. Each narrowing ships with a response-width test and a detail-preserved test.
Emit analysis_* and remediation_* lifecycle pushes from post-commit, env-holding sites in the security-auto-analysis service. The web internal notifications route accepts a lifecycle body and dispatches push only; the notifications worker gains a security_lifecycle dispatch kind gated on securityFindingsEnabled. Terminal emits fire only when the persist actually made the attempt terminal.
iscekic added 21 commits August 22, 2026 13:28
Map only an unmatched gitUrl to Connect repository and a matched repo with an unresolved model to Back to sessions. Set persistent retry guidance on repository fetch failure. Open GitHub integration setup instead of the expired repo-picker and clear terminal guidance after the Connect action runs. Cover the Connect action with a unit test.
…tus bar

Replace the half-height scrim sheet with an opaque full-window Modal. The
surface pads the top safe-area inset, so the content clears the system
status bar. iOS keeps the native pageSheet.
The next line already discards the Android detent on iOS, so the guard
changed nothing.
The layer covered the session while the transparent Android Modal slid
out. The Modal is now opaque and full height, so the cover is dead
weight. Three recorded Back dismissals show no white frame without it.
@iscekic
iscekic changed the base branch from main to audit-w6a-security-reviewer-6304 August 22, 2026 22:34
…viewer-6304

# Conflicts:
#	services/cloud-agent-next/src/session/session-registration.ts
#	services/session-ingest/src/dos/SessionIngestDO.test.ts
#	services/session-ingest/src/dos/SessionIngestDO.ts
Base automatically changed from audit-w6a-security-reviewer-6304 to main August 24, 2026 16:30
@iscekic
iscekic enabled auto-merge (squash) August 24, 2026 16:34
…elease-ff5f

Main carries the merged w6a (#5428), which moved the remediation ledger admit
into the Worker before the queue hand-off. Take main for every conflicted
security file and drop the stale web-side admit in the retry handler.
@iscekic
iscekic merged commit 5f09f35 into main Aug 24, 2026
16 checks passed
@iscekic
iscekic deleted the audit-w6b-bootstrap-release-ff5f branch August 24, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants