@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,360 @@
1
+ # Releasing the React Native SDK
2
+
3
+ The release is driven by `release-compatibility.json`; it is not assembled from
4
+ whatever happens to be at the tips of sibling repositories. CI reads the
5
+ locked commits from that file, checks out those exact objects, verifies every
6
+ SDK contract lock, rebuilds and checks the full core contract bundle, and then
7
+ builds real React Native 0.82 hosts.
8
+
9
+ ## Single-maintainer v1 publication profile
10
+
11
+ The additive `single-maintainer-release.yml` workflow is the explicit launch
12
+ path for `1.0.0` while independent human review and external device/provider
13
+ evidence remain deferred. It accepts only an exact `main` commit, validates all
14
+ four locked sibling source commits, and records
15
+ `single_maintainer_v1`, `release_qualified: false`, and the forbidden stronger
16
+ claims before proceeding.
17
+
18
+ Before any React Native tag mutation, the workflow requires exact annotated
19
+ `v1.0.0` tags for core, JavaScript, iOS, and Android; a byte-identical locked
20
+ `@latchway/client` npm archive; and the exact CocoaPods source binding. The
21
+ Android dependency gate authenticates the exact finalized 14-asset
22
+ `single_maintainer_v1` GitHub release, annotated tag message, per-asset GitHub
23
+ build provenance, maintainer intent, completion record, and the declared
24
+ deferred-evidence set against the Android commit in
25
+ `release-compatibility.json`. It then compares every Maven Central primary
26
+ artifact and checksum byte with the reviewed repository archive, compares every
27
+ public OpenPGP signature with the signed Portal candidate, and independently
28
+ verifies each signature against the release public key. A POM or coordinate
29
+ existence check is not sufficient. The workflow then runs the complete
30
+ TypeScript, compatibility, deterministic package, consumer, Android host, and
31
+ iOS host gates. Deferring Android physical-device evidence therefore does not
32
+ defer the Android SDK or Maven publication dependency.
33
+
34
+ `pnpm verify:compatibility` also requires both `Package.swift` and the
35
+ `latchway-ios-sdk` entry in `Package.resolved` to use the exact iOS source
36
+ revision in `release-compatibility.json`. Updating the compatibility tuple
37
+ without updating either SwiftPM lock fails ordinary CI and both release paths.
38
+
39
+ Create a `single-maintainer-v1` GitHub environment restricted to `main`, with
40
+ the environment-only `LATCHWAY_RELEASE_CONTROL_POLICY_ID` variable set exactly
41
+ to
42
+ `latchway-release-controls-v1:latchway-react-native-sdk:single-maintainer-v1`.
43
+ The first step of every job that names this environment checks the sentinel
44
+ before any checkout, credential access, OIDC request, or mutation, so a missing
45
+ environment cannot be silently auto-created without the intended controls. It
46
+ contains no npm token and does not require an independent reviewer. Configure
47
+ `@latchway/react-native`'s one npm trusted publisher as organization
48
+ `Latchway`, repository `latchway-react-native-sdk`, workflow file
49
+ `single-maintainer-release.yml`, environment `single-maintainer-v1`, and the
50
+ publish action. npm permits only one trusted publisher: while this tuple is
51
+ active, strict `release.yml` cannot publish the package. A future `strict_full`
52
+ release requires deliberate reconfiguration back to workflow file
53
+ `release.yml` and environment `npm`.
54
+
55
+ ```bash
56
+ gh workflow run single-maintainer-release.yml --ref main \
57
+ -f release_profile=single_maintainer_v1 \
58
+ -f release_commit="$(git rev-parse HEAD)" \
59
+ -f release_version=1.0.0 \
60
+ -f confirmation=publish-v1.0.0-with-deferred-assurance
61
+ ```
62
+
63
+ The additive workflow treats one workflow run as the transaction owner. Its
64
+ intent hash binds the run ID and run attempt into the annotated tag. Before the
65
+ candidate checkout can execute, a source-free step authenticates the GitHub run
66
+ ID, attempt, workflow path, `main` head branch, and requested source commit.
67
+ Dispatch strings reach shell commands only through quoted environment
68
+ variables. Immediately before tag creation, the protected job rechecks that
69
+ the authenticated intent commit, dispatch input, workflow SHA, and current
70
+ public `main` head are identical. Before the tag exists, the intent job rejects
71
+ any pre-existing `v1.0.0` tag unless that tag belongs to this exact
72
+ transaction. Once the tag, npm coordinate, or GitHub
73
+ draft has been created, resume only with **Re-run failed jobs** on that same
74
+ workflow run. Never use **Re-run all jobs** and never start a new workflow
75
+ dispatch after a mutation: either action creates a different intent and the
76
+ early tag-owner guard fails closed. Prerequisite intent, package, and npm
77
+ evidence artifacts are retained for 90 days. The GitHub publisher resumes an
78
+ exact partial draft asset-by-asset, compares every adopted asset byte for byte,
79
+ and finalizes only an exact remote closure; it never overwrites an asset.
80
+
81
+ Before any React Native tag or npm mutation, the workflow authenticates the
82
+ public core `v1.0.0` `single_maintainer_v1` record, annotated core tag, candidate
83
+ attestation, scans, SBOMs, and image digest in the exact registry-only 11-asset
84
+ closure. The signed core record must have `deployment_evidence: {}` and the
85
+ exact `cloud_deployments` deferred entry; deployment archives and claims that
86
+ Compose, Cloud Run, or another target passed are rejected. The locked contract
87
+ commit must be an ancestor of that published core commit. Existing
88
+ `@latchway/react-native@1.0.0` bytes are not sufficient for adoption: npm's
89
+ Sigstore provenance must bind the exact `Latchway/latchway-react-native-sdk`
90
+ repository, `single-maintainer-release.yml`, `main` ref, and source commit, and
91
+ the package signature is audited. Cloud deployments, devices, providers, and
92
+ independent review remain explicitly deferred.
93
+
94
+ This selected profile has no prepublication Administration-token job or
95
+ `single-maintainer-v1-administration` environment. The tag may therefore be
96
+ created before GitHub proves the repository's immutable-release setting; that
97
+ is an explicit assurance reduction, not evidence that the setting passed. The
98
+ final publisher still requires an unchanged release ETag, exact asset and tag
99
+ closure, `immutable: true`, and successful release and per-asset attestation
100
+ verification before it reports success.
101
+
102
+ ## Cross-repository order
103
+
104
+ 1. Create a core **contract checkpoint** that marks the manifest released,
105
+ records the fresh `released_at` value, and rebuilds the deterministic
106
+ contract archive. Commit that checkpoint, but do not publish the core tag or
107
+ image yet.
108
+ 2. Create successor JavaScript, iOS, and Android commits whose `contract.lock`
109
+ files name `v1.0.0`, the exact contract-checkpoint commit, and its new bundle
110
+ hash. Synchronize their public constants, vendored fixtures, and final
111
+ `## [1.0.0]` changelog headings. Create the React Native successor last so
112
+ `release-compatibility.json` pins those three exact green successor commits.
113
+ 3. Build every SDK documentation bundle from its clean successor commit. Import
114
+ those exact bundles into a final core candidate and synchronize the
115
+ documentation mirror. The final core candidate must retain an `api/` tree
116
+ byte-identical to the contract checkpoint; its later documentation and
117
+ release metadata do not change the contract archive.
118
+ 4. Run the candidate, cross-repository, security, provider, device, cloud, and
119
+ operations evidence workflows against that exact five-repository tuple.
120
+ Promote the final core commit only after every non-publication release domain
121
+ passes. Core promotion publishes the image and release, then dispatches the
122
+ exact SDK successor coordinates through the `repository_dispatch` event.
123
+ 5. Publish the exact four-package JavaScript set (`@latchway/client`,
124
+ `@latchway/openai`, `@latchway/vercel-ai`, and `@latchway/langchain`), the
125
+ iOS pod/tag, and all Android Maven artifacts/tag. Their immutable GitHub
126
+ releases, per-asset attestations, public registry bytes, registry signatures,
127
+ and source commits must all match the lock; git tag or package metadata alone
128
+ is not sufficient.
129
+ 6. After the promotion envelope is authenticated, the promotion-dispatched
130
+ `.github/workflows/release.yml` starts three parallel branches: one creates
131
+ or verifies the protected annotated `v<package-version>` tag at the exact
132
+ promoted commit, one authenticates locked source, and one waits for and
133
+ authenticates the immutable JavaScript, iOS, and Android releases. The
134
+ downstream dependency-validation and consumer jobs wait for all three, so
135
+ the irreversible tag exists before those gates execute. They then remove
136
+ local path/repository overrides and build the clean npm consumer plus the
137
+ official iOS and Android example hosts. The manual
138
+ `Published dependency consumer` workflow is an optional independent
139
+ diagnostic; its output is not a release workflow input and does not require
140
+ promotion to be rerun.
141
+ 7. After those internal dependency and consumer gates pass, the same release
142
+ run publishes npm through the trusted publisher and finalizes the immutable
143
+ GitHub release. Operators must not create or push the tag manually. Because
144
+ the protected tag already exists, a later dependency, consumer, npm, or
145
+ GitHub-release failure can strand that semantic version; never move or reuse
146
+ it. Correct the inputs and restart the cross-repository sequence with a new
147
+ version.
148
+
149
+ The released-lock successor tuple now satisfies the source transition in steps
150
+ 1 through 3. It is still not independently publishable: the protected evidence,
151
+ promotion, public dependency, and registry gates in steps 4 through 7 remain
152
+ mandatory. The coordinated successor sequence is a required release
153
+ transition, not a post-publication cleanup.
154
+
155
+ `pnpm release:preflight -- v<package-version>` intentionally fails for a dirty
156
+ tree, lightweight/wrong-commit tag, mismatched version, unpublished core lock,
157
+ missing changelog section, local dependency, or forbidden generated/secret
158
+ file.
159
+
160
+ ## npm and GitHub configuration
161
+
162
+ The inert `@latchway/react-native@0.0.0-bootstrap.0` package record must exist
163
+ under the `bootstrap` dist-tag before stable promotion. Configure that package's
164
+ npm trusted publisher with organization `Latchway`, repository
165
+ `latchway-react-native-sdk`, workflow filename `release.yml`, environment
166
+ `npm`, and allowed action `npm publish`. Configure four protected GitHub
167
+ environments: `private-sibling-read`, `npm`, `release-administration`, and
168
+ `github-release`. Every environment must require at least one reviewer, set
169
+ `prevent_self_review: true`, use an exact main-only custom deployment branch
170
+ rule with no tag policy, and disable administrator bypass wherever the repository
171
+ plan exposes that control. A human or administrator is never a release-control
172
+ bypass actor; the active `refs/tags/v*` ruleset may allow only the GitHub Actions
173
+ integration used by this workflow to create the tag.
174
+
175
+ Each environment must define exactly one environment variable named
176
+ `LATCHWAY_RELEASE_CONTROL_POLICY_ID`, with no repository- or organization-level
177
+ fallback:
178
+
179
+ - `private-sibling-read`:
180
+ `latchway-release-controls-v1:latchway-react-native-sdk:private-sibling-read`
181
+ - `npm`: `latchway-release-controls-v1:latchway-react-native-sdk:npm`
182
+ - `release-administration`:
183
+ `latchway-release-controls-v1:latchway-react-native-sdk:release-administration`
184
+ - `github-release`:
185
+ `latchway-release-controls-v1:latchway-react-native-sdk:github-release`
186
+
187
+ Delete any repository or organization variable with that name. The first step
188
+ of every protected release job checks the environment-specific value before an
189
+ action, checkout, download, credential, job token, OIDC request, or mutation can
190
+ run; a missing environment that GitHub auto-creates therefore fails closed. The
191
+ `npm` environment is limited to trusted npm publication and retained
192
+ registry evidence; it contains no reusable credential. The
193
+ `release-administration` environment contains only a fine-grained
194
+ `LATCHWAY_GITHUB_RELEASE_ADMIN_TOKEN` with read-only repository Administration
195
+ permission, stored only as an environment secret. Its jobs use
196
+ `permissions: {}`, never check out candidate source,
197
+ and receive neither OIDC nor GitHub content-write authority. The
198
+ `github-release` protects the promotion job that creates or verifies the
199
+ annotated tag as well as the separate draft and final GitHub release mutation
200
+ jobs; those jobs receive neither the administration token nor npm credentials,
201
+ and that environment contains no secret. Do not define any of
202
+ these protected names as a repository or organization secret: environment
203
+ review must never fall back to a broader secret scope. The built-in
204
+ `github.token` used for public reads is not a substitute for a protected secret.
205
+ `Latchway/latchway-react-native-sdk` must be public at
206
+ publication time because the required npm provenance is not generated for a
207
+ private source repository. The workflow runs on a GitHub-hosted runner with
208
+ `id-token: write`, npm 11.6.2, and provenance-enabled publication. A separate
209
+ source-free `permissions: {}` job downloads the exact
210
+ npm 11.6.2 registry tarball with lifecycle scripts disabled and authenticates a
211
+ one-file artifact closure: 2,663,834 bytes, 2,133 regular entries, 11,785,613
212
+ unpacked bytes, SHA-256
213
+ `585f95094ee5cb2788ee11d90f2a518a7c9ef6e083fa141d0b63ca3383675a20`, and
214
+ integrity
215
+ `sha512-7iKzNfy8lWYs3zq4oFPa8EXZz5xt9gQNKJZau3B1ErLBb6bF7sBJ00x09485DOvRT2l5Gerbl3VlZNT57MxJVA==`.
216
+ The OIDC job rechecks that predeclared name-only closure, byte size, SHA-256,
217
+ SHA-512, integrity, member paths and types, and unpacked size before extraction
218
+ or execution. It invokes the verified CLI directly and never runs `npm install`,
219
+ `npm exec`, or `npx` while holding OIDC or attestation permissions. Before any
220
+ draft or asset mutation, a source-free `release-administration` job requires
221
+ GitHub's exact immutable-release settings response to report
222
+ `enabled: true` and `enforced_by_owner: true`, and requires the installed
223
+ GitHub CLI to support JSON release and asset attestation verification. Bootstrap the npm package
224
+ record through a separately reviewed one-time procedure if the registry requires
225
+ it; the release workflow never accepts `NPM_TOKEN` or `NODE_AUTH_TOKEN`.
226
+
227
+ Before release, install an active repository ruleset for `refs/tags/v*` that
228
+ allows tag creation only through the GitHub Actions integration used by
229
+ `.github/workflows/release.yml` and denies tag updates, deletion, and
230
+ non-fast-forward changes. Operators and administrators must not create, move,
231
+ or delete the release tag manually. This server-side rule remains an external
232
+ release prerequisite. Annotated tag creation is the one release mutation that
233
+ uses the authenticated core-promotion report and tag ruleset as its authority;
234
+ it deliberately precedes immutable-release authorization and never uses the
235
+ administration credential.
236
+
237
+ The v1 release requires the core and all sibling SDK repositories to be public
238
+ before promotion. Keep the historically named `private-sibling-read`
239
+ environment as a credential-free protected approval boundary: it contains no
240
+ secret and must use the reviewer, self-review prevention, main-only branch,
241
+ sentinel, and no-bypass controls above. Never define
242
+ `LATCHWAY_SIBLING_REPOSITORIES_READ_TOKEN` at environment, repository, or
243
+ organization scope. The release and promotion-envelope jobs use only their
244
+ built-in `github.token` for public GitHub API and checkout reads. The locked
245
+ source workflow instead uses a credential-helper-disabled anonymous HTTPS fetch
246
+ for each exact public commit and rejects every credential prompt. A private
247
+ sibling therefore fails closed; it is not supported by the v1 public release
248
+ workflow and must not be enabled by adding a broader-scoped fallback secret.
249
+
250
+ The manual `Published dependency consumer` workflow uses only the job's built-in
251
+ `github.token` for public reads inside a protected `authenticate-inputs` job that
252
+ never checks out or executes the React Native candidate. Fixed commands validate
253
+ the compatibility and
254
+ contract locks at the exact workflow commit, authenticate the locked JavaScript
255
+ source into a Git bundle, capture the immutable tag, release, asset, and build
256
+ attestation evidence, and seal everything into one size-bounded, SHA-256-bound
257
+ artifact. The Android and iOS jobs compare the sealed locks byte for byte with
258
+ their exact candidate checkout, validate the complete artifact manifest, clone
259
+ only the authenticated bundle, and use
260
+ `LATCHWAY_AUTHENTICATED_DEPENDENCY_INPUTS` for offline GitHub evidence. They
261
+ receive no `GH_TOKEN`, `GITHUB_TOKEN`, `NODE_AUTH_TOKEN`, or OIDC request URL.
262
+
263
+ The main-branch/workflow-dispatch `Locked source conformance` workflow applies
264
+ the same split to source builds. Its protected `authenticate-inputs` job has no
265
+ candidate checkout: a fixed GitHub API request reads only
266
+ `release-compatibility.json` and `contract.lock` at the exact workflow commit,
267
+ then a separately scoped, credential-free step fetches the four locked public
268
+ commits and creates their bundles. Another credential-free step seals those
269
+ bundles and locks as an exact six-payload-file, size-bounded, SHA-256-bound
270
+ closure plus its manifest. Fresh JavaScript, Android, and iOS jobs have no
271
+ protected environment, secret, registry authentication, or OIDC permission.
272
+ Before candidate-owned `ci-lock-output.mjs` or build tooling runs, each job
273
+ asserts that credential and OIDC variables are empty, compares both locks byte
274
+ for byte with the exact candidate, validates the complete archive manifest, and
275
+ imports all four bundles offline. Pull-request `ci.yml` contains no secret
276
+ reference.
277
+
278
+ The first source-free `release-administration` preflight waits for the full
279
+ verification, Android, iOS, and authenticated npm-CLI prerequisites. It emits a
280
+ canonical JSON lease plus its SHA-256, bound to the exact repository, release
281
+ coordinates, workflow run and attempt, `draft-and-npm` phase, and the exact
282
+ owner-enforced immutable-release settings. Run and attempt identifiers are
283
+ positive, bounded JSON integers no larger than 9,007,199,254,740,991; strings,
284
+ fractions, zero, and larger values are rejected. The lease lifetime is at most
285
+ 600 seconds and its validity interval is half-open:
286
+ `issued_at_epoch <= now < expires_at_epoch`, so the exact expiration second is
287
+ unauthorized. It travels only as canonical JSON and SHA-256 scalar job outputs,
288
+ never as a downloadable
289
+ archive or artifact whose nested filesystem closure could be expanded. Both the
290
+ draft and npm publication jobs reject a noncanonical or oversized JSON closure,
291
+ hash mismatch, wrong run or attempt, wrong phase, repository or release
292
+ substitution, extra field, non-owner-enforced setting, future issue time, or
293
+ expired or overlong lease. They recheck the lease immediately before draft
294
+ creation, npm provenance attestation, and the first npm publish.
295
+
296
+ After that preflight, the separately reviewed `github-release` job resolves the
297
+ remote annotated tag object to the promoted commit immediately before it creates
298
+ or resumes the fixed-asset GitHub draft before npm publication, but does not
299
+ publish that release until every asset is attached. A second no-checkout,
300
+ no-OIDC `release-administration` job rechecks the immutable-release setting and
301
+ emits a separate `final-github-release` JSON and SHA-256 lease. The final
302
+ `github-release` OIDC job receives no administration credential, validates that
303
+ lease before tooling, and rechecks it immediately before its public provenance
304
+ attestation and every GitHub asset upload or release-finalization mutation. It
305
+ validates the exact local asset closure before attesting it. It checks an existing npm version by
306
+ exact tarball bytes and SHA-512 for safe retry, then retains the bounded raw npm
307
+ registry, `npm view --json --include-attestations`, Sigstore, and
308
+ `npm audit signatures` outputs as hash-bound release assets. Project and user
309
+ configuration cannot redirect these scoped operations: each npm or
310
+ pnpm network command pins both the default registry and `@latchway:registry` to
311
+ `https://registry.npmjs.org/` at CLI precedence, while isolated user, global,
312
+ project, and cache configuration removes inherited registry overrides.
313
+ Existing GitHub assets are downloaded and compared byte for byte, only missing
314
+ draft assets are attached, and a mismatched or incomplete final release stops
315
+ the run. After
316
+ finalization and every release/asset attestation verification, it fetches the
317
+ remote tag ref and annotated tag object again and requires the exact tag name
318
+ and promoted commit binding. Bounded retries of `gh release verify` and
319
+ `gh release verify-asset` are parsed with duplicate-key rejection: the signed
320
+ source commit and exact asset-name/SHA-256 closure must match every fixed or
321
+ adoption-history asset.
322
+
323
+ If an npm publish succeeds but a later step fails, only GitHub's **Re-run all
324
+ jobs** operation or a fresh promotion dispatch may adopt that immutable version,
325
+ and only after rechecking its exact bytes, signatures, and source provenance.
326
+ Never use **Re-run failed jobs**: successful lease-producing jobs would retain
327
+ the previous `run_attempt`, while their consumers execute under the new attempt
328
+ and correctly reject the stale lease. The attested adoption record binds the
329
+ original provenance-producing run and attempt, the current successful run and
330
+ attempt, and the exact retained registry evidence manifest. The
331
+ published-dependency gate applies the same standard to the locked JavaScript,
332
+ iOS, and Android releases. React Native links
333
+ only `@latchway/client`, but the JavaScript release is indivisible: the gate
334
+ requires its exact 31 fixed assets, all four package archives, the version 2
335
+ reviewed package-set and registry-manifest schemas, the version 2 publish-input
336
+ schema, the version 3 publication schema, and at least one package-suffixed
337
+ adoption record for each package. It independently checks the client entry,
338
+ four-package order, per-package retained-output names and hashes, byte identity
339
+ from each GitHub archive through npm, trusted-publisher provenance, and live
340
+ signature audit for every package. It also verifies every strictly parsed
341
+ automatic release/asset attestation, annotated source tag, source-bound workflow
342
+ attestation, and each live registry byte. It independently verifies Maven
343
+ signatures against the attested
344
+ public key with a fail-closed GnuPG status allowlist that rejects revoked,
345
+ expired, unknown, or weak signatures. An interrupted exact promotion can
346
+ therefore be retried only by re-running all jobs or starting a fresh dispatch.
347
+
348
+ Do not manually create or retag a failed release or overwrite a published npm version.
349
+ Fix the release inputs, choose a new semantic version, and rerun the complete
350
+ cross-repository sequence.
351
+
352
+ ## Gates that require external infrastructure
353
+
354
+ Repository CI cannot manufacture registry credentials, published CocoaPods or
355
+ Maven coordinates, a released core tag/image, Apple/Google provider
356
+ configuration, signing identities, Play distribution, or physical devices.
357
+ Those are explicit release or device-conformance gates, not reasons to weaken
358
+ source verification. See [native installation](native-installation.md) and
359
+ [conformance](conformance.md) for the device evidence required after the source
360
+ and published-consumer builds are green.
@@ -0,0 +1,55 @@
1
+ # Security guidance
2
+
3
+ ## Application responsibilities
4
+
5
+ - Supply an identity JWT only from the signed-in application's identity provider. Do not persist it for Latchway.
6
+ - Configure a gateway origin, never an upstream provider endpoint.
7
+ - Never add provider keys, service-account credentials, App Attest evidence, or Play Integrity tokens to options or request headers.
8
+ - Use `client.fetch` or `client.fetchFor(feature)`. No credential-bearing `Request` or authorization envelope is available to JavaScript.
9
+ - Display only `LatchwayError.code`, `documentationURL`, status, request ID, canonical operation ID, retryability, and the already-sanitized message. Documentation links use `https://docs.latchway.dev/errors/<hyphenated-code>`; native server metadata fails closed unless its URL matches the code exactly. Preserve an `operation_indeterminate` operation ID for reconciliation rather than automatic replay.
10
+ - Call `revokeCurrentInstallation()` for explicit removal of the current installation. Call no-argument `revokeCurrentInstallationFamily()` when sign-out must revoke every component in the wire-v2 family and retire the root native key. The iOS SDK discovers previously prepared components through a bounded, validated, non-secret descriptor registry in the root-private Keychain group; failed local erasures remain registered for retry. `dispose()` alone deliberately preserves secure installation state.
11
+
12
+ ## Key policy
13
+
14
+ iOS defaults to Secure Enclave with software fallback disallowed. Android defaults to StrongBox preferred with software-backed keys disallowed. Relaxation is explicit (`apple.softwareKeyFallbackPolicy: "allow"` or `android.keyPolicy: "software_allowed"`) and changes the trust properties reported by the server. Do not silently enable fallback after a native failure.
15
+
16
+ The iOS root client and App Attest provider receive the same explicit private
17
+ Keychain group and the same bounded list of extension-shared groups. Root
18
+ keys, sessions, and App Attest state never use an implicit shared group. The
19
+ App Intents/component path continues to use only its exact shared component
20
+ group and constructs no root App Attest provider.
21
+
22
+ On iOS, sign the root target with its private app-ID Keychain group first and
23
+ the shared component group second. Keychain calls without an explicit access
24
+ group therefore default to root-private storage. Sign an extension with only
25
+ the shared group: it may use explicitly component-scoped handoff/session state,
26
+ but it cannot read the root's private keys, credentials, or sessions. Candidate
27
+ verification rejects any other signed ordering or membership and requires each
28
+ signed group to be authorized by the target's provisioning profile.
29
+
30
+ ## Attestation
31
+
32
+ App Attest and Play Integrity run entirely in their native SDK providers. Server `client_data_hash`/request-hash bindings are consumed internally. JavaScript cannot provide evidence or claim a trust result. Disabling App Attest or omitting the Android cloud project fails closed when the server requires that provider.
33
+
34
+ An iOS application extension cannot call `DCAppAttestService.generateKey`.
35
+ Only the containing root application establishes App Attest for itself, and it
36
+ must never attest on an extension's behalf. The separate extension-process
37
+ client therefore constructs no App Attest provider and cannot acquire the root
38
+ lease; it retains independently keyed, component-scoped delegated sessions and
39
+ returns only redacted diagnostics. Direct-attestation entry points and trust
40
+ source decoders remain for API/wire compatibility, but invocation fails closed
41
+ with `attestation_unsupported` on both platforms and must not be treated as a
42
+ reachable trust result.
43
+
44
+ React Native v1 has no delegated component request API. The example App
45
+ Intents target does not host React Native or call the component bridge and its
46
+ intent fails closed; do not treat its build, installation, or invocation as
47
+ delegated-request evidence.
48
+
49
+ ## Dispatch, replay, and redirects
50
+
51
+ JavaScript validates the exact gateway origin and allowed data-plane path, rejects fragments and decoded credential-query names, strips credential and native-owned headers, and buffers at most 8 MiB of request body before native dispatch. Native repeats the origin/path check, attaches session credentials, refuses redirects, and retains the response task. JavaScript receives a strict safe-header allowlist and pulls at most one bounded response chunk at a time. Abort and stream cancellation cancel the active native operation and close the opaque response handle. JavaScript never clones or replays an authenticated request; only the locked native transport may perform its validated, one-time pre-dispatch retry. On iOS, the feature transport owns a private URL session and caps classification of the first canonical rejection at 64 KiB before it may retry.
52
+
53
+ ## Reporting
54
+
55
+ Follow [SECURITY.md](../SECURITY.md). Reproduce with synthetic tokens and evidence. Never attach live identity/session credentials, DPoP proofs, attestation objects, private keys, provider credentials, or signing files.