@latchway/react-native 0.0.0-bootstrap.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/LatchwayReactNative.podspec +33 -0
  3. package/NOTICE +7 -0
  4. package/README.md +293 -3
  5. package/SECURITY.md +69 -0
  6. package/android/build.gradle.kts +62 -0
  7. package/android/consumer-rules.pro +2 -0
  8. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  9. package/android/gradle/wrapper/gradle-wrapper.properties +9 -0
  10. package/android/gradle.properties +4 -0
  11. package/android/gradlew +251 -0
  12. package/android/gradlew.bat +94 -0
  13. package/android/settings.gradle.kts +33 -0
  14. package/android/src/main/AndroidManifest.xml +3 -0
  15. package/android/src/main/java/dev/latchway/reactnative/LatchwayReactNativePackage.kt +25 -0
  16. package/android/src/main/java/dev/latchway/reactnative/NativeLatchwayModule.kt +964 -0
  17. package/android/src/test/java/dev/latchway/reactnative/NativeLatchwayModuleTest.kt +521 -0
  18. package/babel.cjs +22 -0
  19. package/babel.d.cts +7 -0
  20. package/contract.lock +7 -0
  21. package/docs/architecture.md +145 -0
  22. package/docs/conformance.md +68 -0
  23. package/docs/langchain.md +135 -0
  24. package/docs/native-installation.md +172 -0
  25. package/docs/physical-device-evidence.md +559 -0
  26. package/docs/releasing.md +360 -0
  27. package/docs/security.md +55 -0
  28. package/ios/LatchwayNativeBridge.swift +1551 -0
  29. package/ios/RCTNativeLatchway.h +9 -0
  30. package/ios/RCTNativeLatchway.mm +175 -0
  31. package/lib/client.d.ts +29 -0
  32. package/lib/client.d.ts.map +1 -0
  33. package/lib/client.js +939 -0
  34. package/lib/client.js.map +1 -0
  35. package/lib/component-client.d.ts +15 -0
  36. package/lib/component-client.d.ts.map +1 -0
  37. package/lib/component-client.js +141 -0
  38. package/lib/component-client.js.map +1 -0
  39. package/lib/config.d.ts +25 -0
  40. package/lib/config.d.ts.map +1 -0
  41. package/lib/config.js +261 -0
  42. package/lib/config.js.map +1 -0
  43. package/lib/coordinator.d.ts +19 -0
  44. package/lib/coordinator.d.ts.map +1 -0
  45. package/lib/coordinator.js +167 -0
  46. package/lib/coordinator.js.map +1 -0
  47. package/lib/errors.d.ts +5 -0
  48. package/lib/errors.d.ts.map +1 -0
  49. package/lib/errors.js +201 -0
  50. package/lib/errors.js.map +1 -0
  51. package/lib/index.d.ts +8 -0
  52. package/lib/index.d.ts.map +1 -0
  53. package/lib/index.js +13 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/native/NativeLatchway.d.ts +25 -0
  56. package/lib/native/NativeLatchway.d.ts.map +1 -0
  57. package/lib/native/NativeLatchway.js +3 -0
  58. package/lib/native/NativeLatchway.js.map +1 -0
  59. package/lib/native/bridge.d.ts +5 -0
  60. package/lib/native/bridge.d.ts.map +1 -0
  61. package/lib/native/bridge.js +17 -0
  62. package/lib/native/bridge.js.map +1 -0
  63. package/lib/native-output.d.ts +3 -0
  64. package/lib/native-output.d.ts.map +1 -0
  65. package/lib/native-output.js +43 -0
  66. package/lib/native-output.js.map +1 -0
  67. package/lib/polyfills.d.ts +8 -0
  68. package/lib/polyfills.d.ts.map +1 -0
  69. package/lib/polyfills.js +56 -0
  70. package/lib/polyfills.js.map +1 -0
  71. package/lib/request-id.d.ts +2 -0
  72. package/lib/request-id.d.ts.map +1 -0
  73. package/lib/request-id.js +5 -0
  74. package/lib/request-id.js.map +1 -0
  75. package/lib/runtime-symbols.d.ts +2 -0
  76. package/lib/runtime-symbols.d.ts.map +1 -0
  77. package/lib/runtime-symbols.js +9 -0
  78. package/lib/runtime-symbols.js.map +1 -0
  79. package/lib/testing.d.ts +7 -0
  80. package/lib/testing.d.ts.map +1 -0
  81. package/lib/testing.js +9 -0
  82. package/lib/testing.js.map +1 -0
  83. package/lib/types.d.ts +200 -0
  84. package/lib/types.d.ts.map +1 -0
  85. package/lib/types.js +2 -0
  86. package/lib/types.js.map +1 -0
  87. package/lib/version.d.ts +8 -0
  88. package/lib/version.d.ts.map +1 -0
  89. package/lib/version.js +8 -0
  90. package/lib/version.js.map +1 -0
  91. package/package.json +152 -6
  92. package/react-native.config.cjs +7 -0
  93. package/release-compatibility.json +64 -0
  94. package/src/client.ts +1022 -0
  95. package/src/component-client.ts +158 -0
  96. package/src/config.ts +368 -0
  97. package/src/coordinator.ts +195 -0
  98. package/src/errors.ts +225 -0
  99. package/src/index.ts +53 -0
  100. package/src/native/NativeLatchway.ts +75 -0
  101. package/src/native/bridge.ts +23 -0
  102. package/src/native-output.ts +43 -0
  103. package/src/polyfills.ts +50 -0
  104. package/src/request-id.ts +5 -0
  105. package/src/runtime-symbols.ts +9 -0
  106. package/src/testing.ts +11 -0
  107. package/src/types.ts +241 -0
  108. package/src/version.ts +7 -0
@@ -0,0 +1,145 @@
1
+ # React Native SDK architecture
2
+
3
+ ## Dependency and trust boundary
4
+
5
+ ```text
6
+ React Native application
7
+ └─ @latchway/react-native
8
+ ├─ @latchway/client 1.0.0 (errors and shared transport concepts)
9
+ ├─ Latchway/AppAttest 1.0.0 (iOS)
10
+ ├─ Latchway/AppExtensions 1.0.0 (optional Debug extension target)
11
+ └─ dev.latchway:latchway-okhttp + latchway-play-integrity 1.0.0 (Android)
12
+ ```
13
+
14
+ The core repository owns OpenAPI, error codes, attestation binding, DPoP behavior, and compatibility. This package owns the handwritten React Native API, fetch integration, TurboModule schema, cross-instance lease, abort propagation, stable error projection, and redacted diagnostics. Native SDKs exclusively own installation keys, secure session persistence, platform attestation, DPoP signing, and native single-flight.
15
+
16
+ The gateway, not the SDK, derives user, organization, plan, trust, routing,
17
+ pricing, and quota facts. The SDK never receives an upstream provider
18
+ credential or treats application-supplied values as trusted server facts.
19
+
20
+ ## Contract ownership
21
+
22
+ The Latchway core repository exclusively owns the client OpenAPI, error-code
23
+ registry, protocol compatibility manifest, canonical attestation binding, DPoP
24
+ vectors, canonical request examples, and checksummed contract bundle. A
25
+ contract update must verify checksums, update `contract.lock`, regenerate only
26
+ internal types, rerun shared vectors, and pass conformance against the exact
27
+ core revision. Generated bridge and wire types do not become public API.
28
+
29
+ ## Operation flow
30
+
31
+ 1. JavaScript validates the origin, feature, configuration, request state, and decoded query names; provider-credential names fail before identity acquisition or dispatch.
32
+ 2. The application identity callback returns an external identity JWT.
33
+ 3. The TurboModule passes that JWT transiently to the native SDK while native session work runs.
34
+ 4. Native repeats the exact origin and allowed-path checks, establishes or refreshes a device-bound session, signs a DPoP proof, attaches native-owned protocol headers, and dispatches through its private URLSession or OkHttp client.
35
+ 5. Native refuses redirects, retains the credential-bearing request and response task, and returns only an opaque response identifier, status, and allowlisted safe headers.
36
+ 6. A WHATWG `ReadableStream` pulls bounded base64 response chunks through the TurboModule. Pull demand supplies bridge backpressure; abort, reader cancellation, EOF, client disposal, and invalid metadata all finish or cancel the native handle. JavaScript never clones or replays an authenticated request. Android's locked authenticator and iOS's locked feature transport exclusively own the contract-safe, one-time pre-dispatch retry; iOS bounds rejection classification to 64 KiB before any response bytes become visible.
37
+
38
+ Authorization, DPoP, access tokens, refresh tokens, private keys, and attestation evidence never appear in a native return value. Response bodies are application data, not credential envelopes, and remain incrementally delivered rather than eagerly buffered.
39
+
40
+ The containing application's root client owns the public descriptor lifecycle:
41
+ prepare, replace, descriptor diagnostics, descriptor revoke, and whole-family
42
+ retirement. Descriptors are normalized and snapshotted before any identity
43
+ await; native results must match the same snapshot. Identity is acquired only
44
+ transiently for prepare, replace, revoke, and family retirement. Root-side
45
+ component diagnostics are identity-free. A 65,536-byte JavaScript serialization
46
+ limit keeps oversized descriptor sets from reaching the native bridge.
47
+
48
+ The component path uses a separate extension-process client, not the containing
49
+ app's root client or lease. JavaScript running inside a signed `.appex` can
50
+ supply one validated public descriptor; native configuration rejects a
51
+ containing-app process, selects `.reactNativeIOS`, and retains a
52
+ `LatchwayExtensionClient` with no direct App Attest provider. iOS extensions
53
+ cannot call `DCAppAttestService.generateKey`, and the containing application
54
+ must not attest on their behalf. The extension can use only independently
55
+ keyed, component-scoped delegated sessions. React Native v1 exposes no
56
+ JavaScript component request operation. A second bridge operation returns only
57
+ `LatchwayComponentDiagnostics`; direct-attestation trust-source decoders are
58
+ retained for protocol compatibility but are not reachable proof claims. The
59
+ identity callback is not invoked, and component credentials and DPoP material
60
+ never cross the TurboModule. Both platforms report `attestation_unsupported`
61
+ for the legacy direct operation.
62
+
63
+ The example App Intents extension has no React Native runtime or TurboModule
64
+ bridge. Its Debug target optionally links `Latchway/AppExtensions` and calls the
65
+ native extension client directly to prove an independently keyed delegated
66
+ session and one fully consumed bounded Responses request. The root publishes a
67
+ nonsecret exact-run shared-Keychain challenge immediately before waiting. The
68
+ intent captures it before client construction, rechecks it immediately before
69
+ echoing the run in a bounded receipt, and cannot publish for a superseded run.
70
+ The containing app accepts only its native-captured exact run and deletes both
71
+ challenge and receipt before descriptor-bound family retirement and sign-out;
72
+ abort also deletes both artifacts. This is local integration proof. In Release the
73
+ AppExtensions pod is absent, no executable Latchway client path is compiled,
74
+ and the intent fails closed. Both variants retain distinct root/extension
75
+ bundle identities and provisioning profiles, private-first/shared-second root
76
+ Keychain entitlements, and a shared-only extension entitlement. The root's
77
+ first/default group keeps its key, identity, and session state outside the
78
+ extension's reach.
79
+
80
+ The bridge intentionally implements a bounded fetch subset: method, headers,
81
+ an at-most-8-MiB buffered request body, cancellation, response metadata, and a
82
+ pull-driven response stream. Browser cookie/cache modes, service workers,
83
+ redirect following, streaming uploads, response trailers, and native response
84
+ URL metadata are outside this transport. Framework compatibility therefore
85
+ depends on a real custom-fetch seam and the framework's React Native support;
86
+ the presence of `fetchFor` alone is not a version-support claim.
87
+
88
+ `fetchFor(feature)` also maps the canonical safe
89
+ `X-Latchway-Request-ID` response header to the conventional `X-Request-ID`
90
+ alias without reading the body, so provider SDK failures retain server
91
+ correlation. The native runtime remains the framework identity reported to the
92
+ gateway (`react-native-fetch`); an underlying JavaScript library cannot spoof a
93
+ different SDK/framework pair through caller headers.
94
+
95
+ Feature binding is also protocol binding: a Responses feature cannot dispatch
96
+ Chat Completions, Embeddings, or Anthropic Messages. A consumer set spanning
97
+ those protocols constructs a separate `fetchFor` transport for each configured
98
+ feature instead of multiplexing incompatible endpoints through one identifier.
99
+
100
+ ## Coordination
101
+
102
+ A module-global root lease map is keyed by native-module identity plus gateway/application/environment scope. A separate component lease map adds the component definition and never aliases the root map. Equivalent clients reuse one native client and configuration promise; conflicting security configuration for an active scope is rejected. Reference-counted disposal drops the native object only after the last JavaScript client leaves. The native iOS actor and Android coordinator/mutex prevent session establishment and refresh stampedes.
103
+
104
+ Native persistence namespaces include `react_native_ios` or `react_native_android`. The bridge configures the paired runtime identity, so challenge/grant platform and `X-Latchway-SDK: react-native` cannot disagree. Native compatibility JSON is checked against released contract 1.0.0 and current wire protocol 2 before any operation.
105
+
106
+ ## TurboModule boundary
107
+
108
+ The handwritten spec carries root and component configuration as distinct
109
+ operations, a bounded request description, the root client's transient
110
+ application identity token, opaque response-handle start/read/close operations,
111
+ quota/diagnostic results, public component descriptors for lifecycle and
112
+ delegated-session compatibility, cancellation, and disposal. Root lifecycle
113
+ mutations carry identity transiently; root-side and extension-side component
114
+ diagnostics do not. The legacy direct-attestation operation remains
115
+ ABI-compatible but fails closed. Extension-process component operations have no
116
+ identity-token argument. The spec has no authorization-envelope operation and
117
+ does not return or accept provider attestation evidence, Play request hashes,
118
+ App Attest client-data hashes, session tokens, DPoP proofs, or key material.
119
+ Generated Objective-C++ and Java specs are disposable codegen output, not
120
+ public API.
121
+
122
+ ## Native dependencies
123
+
124
+ Published package metadata pins release coordinates. CocoaPods consumes `Latchway/AppAttest` 1.0.0. Gradle consumes `dev.latchway:latchway-okhttp:1.0.0` and `dev.latchway:latchway-play-integrity:1.0.0`. Development may point `LATCHWAY_NATIVE_REPOSITORY` or `-PlatchwayNativeRepository` at a locally published Maven repository; local file links never enter npm metadata.
125
+
126
+ ## Diagnostics and errors
127
+
128
+ Diagnostics contain version compatibility, platform, secure key-storage category, attestation support/provider, session state/expiration, installation ID/status, server version, and last request/error identifiers. Component diagnostics add only family/component IDs, public definition/access-group identifiers, key/session/grant availability, trust provenance/expiry, and a containing-app action flag. Native key IDs, JWK thumbprints, tokens, proofs, and evidence are excluded. Native errors are bounded, control-character stripped, secret-pattern redacted, and mapped to the shared `LatchwayError` taxonomy. A server-originated native error must carry the exact `https://docs.latchway.dev/errors/<hyphenated-code>` documentation URL; missing or mismatched links fail closed. `operation_indeterminate` alone carries a required canonical reconciliation ID through both native bridges; malformed, missing, contradictory, or otherwise attached operation metadata fails closed.
129
+
130
+ ## Verification boundary
131
+
132
+ Unit and Node conformance tests own public request shaping, fail-closed
133
+ credential-output checks, response pull/backpressure, error projection,
134
+ cancellation, coordination, strict-CSP behavior, and
135
+ canonical vectors. Reproducible code generation proves the handwritten schema
136
+ remains valid. Native consumer builds prove released dependency resolution and
137
+ bridge compilation. Physical-device conformance proves real App Attest and Play
138
+ Integrity behavior, session rotation, quota, streaming, diagnostics, and
139
+ revocation against the exact core image.
140
+
141
+ ## Non-goals
142
+
143
+ This package does not own server policy, provider routing, quota enforcement,
144
+ user-authentication UI, AI request modeling, upstream secrets, native
145
+ cryptography, native attestation verification, or an independent session store.
@@ -0,0 +1,68 @@
1
+ # Conformance
2
+
3
+ Node conformance validates JavaScript request shaping with the explicit testing bridge, malicious credential-field output rejection, native response pull/backpressure, strict-CSP safety, redaction, cancellation, version mismatch, cross-instance coordination, and canonical contract fixtures. It does not emulate hardware trust or assert that a production native transport attached a valid proof.
4
+
5
+ The framework suite under `Conformance/framework` uses 16 applicable stable
6
+ `FW-*` case IDs from the JavaScript SDK suite and binds them to the canonical
7
+ `react-native-fetch` registry entry. It executes, rather than merely imports,
8
+ the exact OpenAI 7.8.0, Vercel AI 7.0.85, and LangChain OpenAI 1.5.10 consumer
9
+ paths. RN-specific `RN-FW-ANTHROPIC-001` separately runs
10
+ `@ai-sdk/anthropic` 4.0.46 against `/v1/messages`, without claiming a canonical
11
+ Anthropic framework registry entry. The official `@anthropic-ai/sdk` 0.120.0
12
+ client is recorded as unsupported because its credential-chain Node filesystem
13
+ imports fail an actual Metro bundle. The deterministic native-boundary fixture
14
+ returns protocol-shaped Responses, Chat Completions, Anthropic Messages,
15
+ embeddings, SSE usage, tool calls, structured output, quota failures, provider
16
+ failures, and retry responses. Tests prove feature binding, safe headers,
17
+ request-ID correlation, cancellation, fresh native operation IDs for framework
18
+ retries, placeholder stripping, exact-origin refusal, and isolation from global
19
+ fetch. The runnable consumer factory requires distinct Responses, Chat,
20
+ Embeddings, and Anthropic feature identifiers because one active Latchway
21
+ feature is bound to exactly one gateway protocol.
22
+
23
+ `RN-FW-REFRESH-001` separately proves the public explicit-refresh operation
24
+ followed by a framework request. It is intentionally not reported as shared
25
+ `FW-BEH-006`: automatic recovery from a pre-dispatch expired native session is
26
+ owned by the platform SDK and requires native/device evidence.
27
+
28
+ `RN-FW-OPAQUE-001` proves the bounded `/proxy/{feature}/...` pathway keeps its
29
+ feature binding through native dispatch. None of the RN-only cases extends the
30
+ shared `FW-*` registry set.
31
+
32
+ This suite deliberately does not manufacture DPoP proofs, refresh credentials,
33
+ or platform attestation in JavaScript. Fresh-proof generation and native
34
+ pre-dispatch retry classification remain native SDK/device evidence. The local
35
+ framework result therefore supports the registry's `experimental` state; it is
36
+ not hosted or physical-device release evidence.
37
+
38
+ Real platform conformance requires the exact core image and real provider configuration:
39
+
40
+ - iOS: App Attest development and production applications on physical devices; session creation, assertion reuse, nonce, refresh rotation, quota, streaming, diagnostics, and revocation.
41
+ - Android: Play-distributed application with Play Integrity standard requests; hardware/StrongBox policy variants, nonce, refresh rotation, quota, streaming, diagnostics, and revocation.
42
+ - Both: identity token expiry/reauthentication, installation platform validation, no secret material in errors/diagnostics, and published native dependency resolution.
43
+
44
+ Provider credentials and signing material belong in protected CI/device infrastructure and are never committed. Missing provider credentials block device conformance only; they do not justify fake evidence or trusted identity fields.
45
+
46
+ The production TurboModule intentionally cannot expose or mutate DPoP/session
47
+ credentials for a JavaScript evidence harness. Replay, proof tamper, session
48
+ credential rotation, and protocol-header mutation therefore have to be taken
49
+ from the separately linked native SDK physical-device reports. The v2 raw
50
+ React Native record omits those native-only claims. Its protected finalizer
51
+ validates the hash-pinned platform-native report, imports only the five exact
52
+ allowlisted proof objects, and rejects missing, failed, renamed, extended, or
53
+ coordinate-substituted proofs. The React Native report remains ineligible
54
+ unless all of its own runtime checks pass (eight on iOS, including direct
55
+ assertion reuse; seven on Android) and every imported proof passes.
56
+
57
+ The repository's native source jobs build official React Native 0.82 iOS and
58
+ Android hosts against the exact locked SDK source releases. The published
59
+ dependency jobs repeat those builds with local CocoaPods paths and Maven
60
+ repositories removed. Neither is physical-device attestation evidence.
61
+
62
+ Before v1 is called device-conformant, retain redacted run evidence for each
63
+ platform/provider/environment combination covering installation creation,
64
+ session reuse, identity expiry and reauthentication, nonce recovery, refresh
65
+ rotation, quota, an SSE stream, diagnostics, revocation, and a post-revocation
66
+ re-enrollment. The evidence must name the application release, device/OS,
67
+ gateway image digest, contract bundle hash, SDK package versions, and request
68
+ IDs without including tokens, proofs, keys, or raw attestations.
@@ -0,0 +1,135 @@
1
+ # LangChain on React Native
2
+
3
+ Use `@latchway/react-native@1.1.0` for native authenticated transport and
4
+ `@latchway/langchain@1.1.0` for the optional LangChain adapter. No provider key
5
+ belongs in the application. Secure Enclave/Keystore, App Attest/Play Integrity,
6
+ DPoP and refresh credentials stay native.
7
+
8
+ ## Install
9
+
10
+ The tested baseline is React Native 0.82 / React 19.1, New Architecture:
11
+
12
+ ```sh
13
+ npm install --save-exact @latchway/react-native@1.1.0 @latchway/langchain@1.1.0 \
14
+ @latchway/client@1.0.0 @langchain/core@1.2.9 @langchain/openai@1.5.10 openai@7.8.0
15
+ cd ios && pod install && cd ..
16
+ ```
17
+
18
+ Keep the lockfile. LangChain is not a dependency of the base React Native SDK.
19
+ The native dependencies remain the public iOS/Android SDKs 1.0.0. Normal native
20
+ signing, Firebase/other identity and gateway platform policy setup still applies.
21
+
22
+ ## Two explicit setup lines
23
+
24
+ Put the bootstrap **first** in the application entrypoint, before importing
25
+ React Native, Latchway, LangChain or any stream-dependent application module:
26
+
27
+ ```js
28
+ import '@latchway/react-native/polyfills';
29
+ import {AppRegistry} from 'react-native';
30
+ import App from './App';
31
+ ```
32
+
33
+ Use the optional compiler helper in `babel.config.js`:
34
+
35
+ ```js
36
+ const {withLatchwayBabel} = require('@latchway/react-native/babel');
37
+ module.exports = withLatchwayBabel({
38
+ presets: ['module:@react-native/babel-preset'],
39
+ });
40
+ ```
41
+
42
+ Use standard Metro; no resolver override, source alias or local SDK link is
43
+ needed. Rebuild the native app after installation. Clear Metro's cache if it
44
+ still resolves the old 1.0.0 source entry.
45
+
46
+ The bootstrap installs missing async symbols before stream dependencies,
47
+ incremental UTF-8 decoding, web streams, random values and abort compatibility.
48
+ It only replaces URL when a capability probe fails, preserves working globals,
49
+ and never replaces fetch or native security. The standard SDK import does not
50
+ initialize globals. The decoder currently uses the pinned, deprecated
51
+ `text-encoding@0.7.0` implementation for incremental decoding; this is a known
52
+ maintenance dependency, not a claim of comprehensive runtime compatibility.
53
+
54
+ The Babel helper enables `noClassCalls` and the export-namespace transform while
55
+ preserving the rest of your config. Classes must still be constructed with
56
+ `new`; LangChain's own type checks are not removed.
57
+
58
+ ## Create a model
59
+
60
+ Create `latchway` using the ordinary SDK configuration and your current user's
61
+ identity-token callback. Then pass that client directly—no transport wrapper:
62
+
63
+ ```ts
64
+ import {createLatchwayResponsesModel} from '@latchway/langchain';
65
+
66
+ const model = createLatchwayResponsesModel({
67
+ latchway,
68
+ feature: 'assistant',
69
+ reasoning: {effort: 'none'}, // only if your configured model supports this
70
+ chatOptions: {maxTokens: 1024},
71
+ });
72
+ const controller = new AbortController();
73
+ for await (const chunk of await model.stream('Explain Latchway', {
74
+ signal: controller.signal,
75
+ })) {
76
+ // Render text chunks; retain original messages for usage/callback metadata.
77
+ }
78
+ // controller.abort() cancels native networking too.
79
+ ```
80
+
81
+ The feature must route `openai_responses`, with the matching trusted accounting
82
+ profile, on gateway 1.0.2+. The server selects the physical model. The helper
83
+ does not guess reasoning capabilities from the placeholder model alias: omit
84
+ `reasoning` for routes that do not accept it. Stateless storage settings do not
85
+ guarantee retention behavior across all upstream providers.
86
+
87
+ For an `openai_chat` feature use the existing `createLatchwayChatOpenAI` instead.
88
+ Both factories default to no automatic framework retries. Native pre-dispatch
89
+ session recovery is separate; uncertain provider dispatches must not be replayed
90
+ casually. Explicit `chatOptions.maxRetries` is an application policy choice.
91
+
92
+ ## Tools and local history
93
+
94
+ ```ts
95
+ import {bindLatchwayTools, toLatchwayReplayMessage} from '@latchway/langchain';
96
+ import {HumanMessage, type BaseMessage} from '@langchain/core/messages';
97
+
98
+ // weather is your ordinary LangChain tool; the SDK never executes tools for you.
99
+ const modelWithTools = bindLatchwayTools(model, [weather]);
100
+ const history: BaseMessage[] = [new HumanMessage('Weather in Singapore?')];
101
+ const answer = await modelWithTools.invoke(history);
102
+ const replay = toLatchwayReplayMessage(answer);
103
+ history.push(replay);
104
+ // Dispatch only an allowlisted tool with validated arguments. Append the
105
+ // returned ToolMessage, preserving the model's tool_call_id, then invoke again.
106
+ ```
107
+
108
+ The binding helper
109
+ defaults to `strict:true` and `parallel_tool_calls:false`; ordinary LangChain
110
+ options can override these deliberately. Supply strict-compatible schemas
111
+ (all required fields, `additionalProperties:false`; nullable instead of optional
112
+ properties when necessary).
113
+
114
+ Use `toLatchwayReplayMessage` only after a successful complete response (or
115
+ aggregating all stream chunks). It preserves text and function-call IDs while
116
+ removing provider item IDs and observational metadata. It rejects non-text,
117
+ opaque reasoning/refusal, malformed calls and duplicate IDs. Keep the original
118
+ message separately for usage information. Do not save failed/partial turns as
119
+ history. The helper does not store history, execute tools or enforce loop limits.
120
+
121
+ Set explicit tool-call limits, timeouts, cancellation and history bounds in your
122
+ app. Standard LangChain `withStructuredOutput(..., {method:'jsonSchema', strict:true})`
123
+ is available when the gateway route/model supports the schema.
124
+
125
+ ## Example and evidence
126
+
127
+ The repository's `Examples/LatchwayChat` demonstrates Firebase login, a bounded
128
+ streaming weather-tool loop, ephemeral chat, direct fetch, Stop and diagnostics.
129
+ It consumes npm packages. Its verification report distinguishes physical iOS
130
+ evidence from Android build-only evidence. Request framework attribution remains
131
+ native-owned `react-native-fetch`, including when called through LangChain.
132
+
133
+ References: [Metro exports](https://metrobundler.dev/docs/package-exports/),
134
+ [Babel assumptions](https://babeljs.io/docs/assumptions#noclasscalls),
135
+ [LangChain ChatOpenAI](https://docs.langchain.com/oss/javascript/integrations/chat/openai).
@@ -0,0 +1,172 @@
1
+ # Native installation
2
+
3
+ ## iOS
4
+
5
+ Configure `apple.rootKeychainAccessGroup` with the fully resolved private app-ID
6
+ group that appears first in the signed root target. List every explicit
7
+ extension-shared group in `apple.legacySharedKeychainAccessGroups`; the native
8
+ SDK scans only exact root-record coordinates in those groups. Missing,
9
+ wildcard, duplicate, or root-equal groups fail closed, and stale root records
10
+ require an explicit migration.
11
+
12
+ The podspec pins `Latchway/AppAttest` 1.0.0 and React Native codegen dependencies. Run CocoaPods from the host application after installing the npm package. Enable App Attest for the application identifier and use a real device for conformance; simulators report attestation unsupported.
13
+
14
+ The Firebase Authentication example pins React Native Firebase 25.1.0 and
15
+ Firebase Apple SDK 12.15.0 and uses CocoaPods static frameworks. The Latchway
16
+ package itself does not depend on Firebase. Firebase has announced that the
17
+ existing CocoaPods releases remain installable but new Firebase Apple SDK
18
+ versions stop shipping through CocoaPods after October 2026; migrate the
19
+ example to the compatible React Native Firebase SPM path only after its pinned
20
+ RN 0.82 native host build is green.
21
+
22
+ For a source-development run on a physical iPhone or iPad, the example offers a
23
+ separate opt-in Debug bootstrap. `scripts/copy-development-firebase-ios-config.sh`
24
+ validates an external, bundle-matched Firebase plist and copies it only into a
25
+ Debug `iphoneos` build; `scripts/run-development-react-native-ios.sh` keeps the
26
+ custom token and digest out of an allowlisted Xcode build environment, validates
27
+ the complete non-secret deployment coordinates, rechecks grant freshness, and
28
+ force-bundles the exact JavaScript checkout before handing the grant to one
29
+ no-debugger launch. The physical-device run therefore does not require Metro or
30
+ Local Network access, although iOS can still show React Native's one-time Debug
31
+ permission sheet on the first install. Later runs update the existing app so
32
+ that OS consent persists. The app signs in with a new grant, revokes the old
33
+ descriptor-bound family, verifies the root Responses/quota/diagnostics path,
34
+ and prepares the App Intent descriptor. After the one-use grant has been
35
+ destroyed, the root publishes a nonsecret exact-run shared-Keychain challenge
36
+ immediately before the waiting marker. The separately launched Debug App Intent
37
+ captures that challenge before constructing its client, refreshes an
38
+ independently keyed delegated session, and fully consumes one successful bounded
39
+ Responses body. It rechecks the challenge immediately before echoing the run in
40
+ a bounded shared-Keychain receipt. The containing app accepts only its
41
+ native-captured exact run, deletes both artifacts, retires that exact
42
+ descriptor-bound family, and signs out. The runner passes only after retrieving
43
+ the exact random-run
44
+ marker written after terminal cleanup. Its bounded post-wait abort path also
45
+ relaunches the containing app to finish and verify family retirement/sign-out
46
+ after interruption or timeout, deleting both challenge and receipt. The Debug
47
+ native module and marker writer are
48
+ absent from Release. This path verifies local integration only and cannot
49
+ satisfy the protected physical-evidence gate. See the example README for the
50
+ exact runner workflow and possible Shortcuts tap.
51
+
52
+ Firebase Authentication and Firebase App Check are distinct. The checked-in
53
+ example pins Firebase App/Auth but does not install the native App Check module,
54
+ and a Firebase web App Check registration does not apply to an iOS application.
55
+ When App Check enforcement is enabled for the Firebase resource, pin a
56
+ compatible React Native Firebase App Check dependency and activate the Apple
57
+ App Attest provider before the Auth exchange. A debug App Check provider/token
58
+ is never acceptable in a protected Release candidate.
59
+
60
+ A production App Attest run requires all of the following, none of which can be
61
+ substituted by a simulator build:
62
+
63
+ - an App Attest-capable physical iPhone or iPad;
64
+ - a registered App ID with the App Attest capability, a matching Team ID and
65
+ bundle ID, and a provisioning profile containing the entitlement;
66
+ - `development` or `production` selected consistently in the entitlement,
67
+ React Native client configuration, gateway application record, and Apple
68
+ verification policy; and
69
+ - a real application identity token plus the exact gateway/core release named
70
+ by the synchronized contract lock.
71
+
72
+ The root-application bridge constructs the App Attest provider with the exact
73
+ `rootKeychainAccessGroup`, `legacySharedKeychainAccessGroups`, and
74
+ `.reactNativeIOS` runtime, then passes the same groups to
75
+ `LatchwayConfiguration`. Keychain, Secure Enclave, session, and accepted App
76
+ Attest key state are runtime-isolated. An extension bridge constructs no App
77
+ Attest provider: iOS app extensions cannot call
78
+ `DCAppAttestService.generateKey`, so extension sessions remain independently
79
+ keyed and delegated from the already attested root application.
80
+
81
+ The checked-in App Intents target has two intentionally different build
82
+ boundaries. In Debug, its own CocoaPods target links `Latchway/AppExtensions`
83
+ and the native Swift intent performs the local delegated-request proof without
84
+ hosting a React Native JavaScript runtime. In Release, that dependency is not
85
+ linked, no executable Latchway client path is compiled, and the archive/signing
86
+ fixture's intent fails closed. The CocoaPods subspec is imported through module
87
+ `Latchway`, not the SwiftPM-only module name.
88
+
89
+ Candidate production requires a distinct child bundle ID and provisioning
90
+ profile. The signed root target lists its private app-ID Keychain group first
91
+ and the shared component group second; the first position keeps implicit root
92
+ Keychain writes private. The signed extension lists only the shared group and
93
+ therefore cannot read root-private key, credential, identity, or session state.
94
+ Each provisioning profile must authorize every group its target signs, either
95
+ exactly or with a well-formed terminal wildcard. The extension must not carry
96
+ App Attest. The Debug intent constructs its delegated client with
97
+ `.reactNativeIOS`; the gateway component definition must therefore use platform
98
+ `react_native_ios`, kind `app_intent_extension`, delegated-only trust, and the
99
+ same requested feature as the descriptor prepared by the root.
100
+
101
+ The root JavaScript API owns the descriptor lifecycle:
102
+
103
+ - `prepareComponents` provisions one or more exact descriptors;
104
+ - `replaceComponent` rotates/replaces one exact descriptor;
105
+ - root-side `componentDiagnostics` reads redacted local state without acquiring
106
+ application identity;
107
+ - `revokeComponent` retires one descriptor; and
108
+ - no-argument `revokeCurrentInstallationFamily()` retires the root plus every
109
+ component in the native iOS SDK's durable root-private descriptor registry;
110
+ the optional descriptor list additionally covers pre-registry legacy state.
111
+
112
+ Descriptors are normalized and snapshotted before asynchronous identity work.
113
+ The native SDK registers only their public Keychain coordinates before it can
114
+ create component-local state. Successful cleanup removes a coordinate, while a
115
+ failed Keychain erasure keeps it durable for retry after a later app launch.
116
+ Preparation, replacement, and returned diagnostics are checked against that
117
+ same snapshot, and serialized multi-component input larger than 65,536 bytes is
118
+ rejected in JavaScript before crossing the native bridge. Component keys,
119
+ grants, delegated sessions, and the root identity never cross into JavaScript.
120
+
121
+ For local native SDK work, declare the sibling `Latchway.podspec` by path in the
122
+ appropriate host targets. The root path declaration lets the autolinked React
123
+ Native pod resolve its App Attest dependency from that source, while the
124
+ extension subspec is Debug-only:
125
+
126
+ ```ruby
127
+ target "ContainingApp" do
128
+ pod "Latchway", :path => "../../../latchway-ios-sdk"
129
+ # use_native_modules! / use_react_native! follows here.
130
+ end
131
+
132
+ target "AppIntents" do
133
+ pod "Latchway/AppExtensions", :path => "../../../latchway-ios-sdk",
134
+ :configurations => ["Debug"]
135
+ end
136
+ ```
137
+
138
+ Local paths belong only in the host Podfile. They are absent from the published React Native podspec.
139
+
140
+ ## Android
141
+
142
+ The library pins the native 1.0.0 Maven coordinates. Those AARs publish Kotlin
143
+ 2.3 metadata and require compile SDK 37; the React Native 0.82 host therefore
144
+ pins Kotlin 2.3.21 while retaining RN's supported consumer AGP 8.12 baseline.
145
+ Play Integrity requires the decimal Google Cloud project number in
146
+ `android.playIntegrityCloudProjectNumber`; JavaScript never receives or
147
+ supplies the resulting integrity token.
148
+
149
+ A production Play Integrity run requires a physical device and a build whose
150
+ package name and signing-certificate digest match the gateway application
151
+ record. Configure the Play Integrity API and its decimal Google Cloud project
152
+ number, upload the signed build to a Play internal/closed/production track, and
153
+ install it from Google Play. A locally sideloaded debug APK can prove compilation
154
+ and bridge behavior, but it is not production Play Integrity evidence. Exercise
155
+ both hardware-backed/StrongBox-available and fallback policy variants on the
156
+ device matrix required by the deployment.
157
+
158
+ For local native SDK work, publish the native artifacts to a disposable Maven repository and set one of:
159
+
160
+ ```sh
161
+ ./android/gradlew -p android -PlatchwayNativeRepository=/absolute/path/to/maven check
162
+ ```
163
+
164
+ ```sh
165
+ LATCHWAY_NATIVE_REPOSITORY=/absolute/path/to/maven ./android/gradlew -p android check
166
+ ```
167
+
168
+ The repository is content-filtered to `dev.latchway`. Composite-build substitution is intentionally avoided because the Android SDK and React Native 0.82 currently use incompatible Gradle major versions.
169
+
170
+ ## Release dependency check
171
+
172
+ CI must run once against only the published CocoaPods/Maven coordinates, without local path or repository overrides, before npm publication. A local native build proving source compatibility does not replace that consumer check. The manual `Published dependency consumer` workflow performs that gate; the promotion-dispatched release workflow repeats it before npm publication.