@latchway/react-native 0.0.0-bootstrap.0 → 1.0.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.
- package/CHANGELOG.md +97 -0
- package/LatchwayReactNative.podspec +33 -0
- package/NOTICE +7 -0
- package/README.md +286 -3
- package/SECURITY.md +69 -0
- package/android/build.gradle.kts +62 -0
- package/android/consumer-rules.pro +2 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/android/gradle.properties +4 -0
- package/android/gradlew +251 -0
- package/android/gradlew.bat +94 -0
- package/android/settings.gradle.kts +33 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/dev/latchway/reactnative/LatchwayReactNativePackage.kt +25 -0
- package/android/src/main/java/dev/latchway/reactnative/NativeLatchwayModule.kt +964 -0
- package/android/src/test/java/dev/latchway/reactnative/NativeLatchwayModuleTest.kt +521 -0
- package/contract.lock +7 -0
- package/docs/architecture.md +145 -0
- package/docs/conformance.md +68 -0
- package/docs/native-installation.md +172 -0
- package/docs/physical-device-evidence.md +559 -0
- package/docs/releasing.md +360 -0
- package/docs/security.md +55 -0
- package/ios/LatchwayNativeBridge.swift +1551 -0
- package/ios/RCTNativeLatchway.h +9 -0
- package/ios/RCTNativeLatchway.mm +175 -0
- package/lib/client.d.ts +29 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +939 -0
- package/lib/client.js.map +1 -0
- package/lib/component-client.d.ts +15 -0
- package/lib/component-client.d.ts.map +1 -0
- package/lib/component-client.js +141 -0
- package/lib/component-client.js.map +1 -0
- package/lib/config.d.ts +25 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +261 -0
- package/lib/config.js.map +1 -0
- package/lib/coordinator.d.ts +19 -0
- package/lib/coordinator.d.ts.map +1 -0
- package/lib/coordinator.js +167 -0
- package/lib/coordinator.js.map +1 -0
- package/lib/errors.d.ts +5 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +201 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/lib/native/NativeLatchway.d.ts +25 -0
- package/lib/native/NativeLatchway.d.ts.map +1 -0
- package/lib/native/NativeLatchway.js +3 -0
- package/lib/native/NativeLatchway.js.map +1 -0
- package/lib/native/bridge.d.ts +5 -0
- package/lib/native/bridge.d.ts.map +1 -0
- package/lib/native/bridge.js +17 -0
- package/lib/native/bridge.js.map +1 -0
- package/lib/native-output.d.ts +3 -0
- package/lib/native-output.d.ts.map +1 -0
- package/lib/native-output.js +43 -0
- package/lib/native-output.js.map +1 -0
- package/lib/request-id.d.ts +2 -0
- package/lib/request-id.d.ts.map +1 -0
- package/lib/request-id.js +5 -0
- package/lib/request-id.js.map +1 -0
- package/lib/testing.d.ts +7 -0
- package/lib/testing.d.ts.map +1 -0
- package/lib/testing.js +9 -0
- package/lib/testing.js.map +1 -0
- package/lib/types.d.ts +200 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +8 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +8 -0
- package/lib/version.js.map +1 -0
- package/package.json +130 -6
- package/react-native.config.cjs +7 -0
- package/release-compatibility.json +64 -0
- package/src/client.ts +1022 -0
- package/src/component-client.ts +158 -0
- package/src/config.ts +368 -0
- package/src/coordinator.ts +195 -0
- package/src/errors.ts +225 -0
- package/src/index.ts +53 -0
- package/src/native/NativeLatchway.ts +75 -0
- package/src/native/bridge.ts +23 -0
- package/src/native-output.ts +43 -0
- package/src/request-id.ts +5 -0
- package/src/testing.ts +11 -0
- package/src/types.ts +241 -0
- 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.
|
package/docs/security.md
ADDED
|
@@ -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.
|