also looking at this
fix(mobile): preserve native editing, navigation, and durable media requests
#369
36 files
+864
-185
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | name: CI |
| 2 | 2 | |
| 3 | 3 | on: |
| 4 | workflow_call: {} | |
| 4 | 5 | workflow_dispatch: |
| 5 | 6 | inputs: |
| 6 | 7 | software_cold_provisioning_diagnostic: |
| @@ -15,7 +16,7 @@ on: | ||
| 15 | 16 | - main |
| 16 | 17 | |
| 17 | 18 | concurrency: |
| 18 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| 19 | group: source-ci-${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| 19 | 20 | # Finish Main acceptance; newer commits replace the single pending run. |
| 20 | 21 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} |
| 21 | 22 | |
| @@ -27,6 +27,22 @@ jobs: | ||
| 27 | 27 | - name: Review dependency advisories |
| 28 | 28 | run: bun scripts/advisory-policy.ts |
| 29 | 29 | |
| 30 | # Relative reusable workflows check the exact tagged commit. Main may keep | |
| 31 | # moving while these full suites run; publication remains tied to this tag. | |
| 32 | source-acceptance: | |
| 33 | name: accept exact release source | |
| 34 | needs: validate-release | |
| 35 | uses: ./.github/workflows/ci.yml | |
| 36 | permissions: | |
| 37 | contents: read | |
| 38 | ||
| 39 | native-acceptance: | |
| 40 | name: accept exact native runtimes | |
| 41 | needs: validate-release | |
| 42 | uses: ./.github/workflows/mobile-e2e.yml | |
| 43 | permissions: | |
| 44 | contents: read | |
| 45 | ||
| 30 | 46 | release-sbom: |
| 31 | 47 | needs: validate-release |
| 32 | 48 | uses: ./.github/workflows/sbom.yml |
| @@ -38,7 +54,7 @@ jobs: | ||
| 38 | 54 | pantry: |
| 39 | 55 | # The reusable SBOM workflow includes the vulnerability scan. Publishing |
| 40 | 56 | # must wait for its existing Critical gate, not merely attach it afterward. |
| 41 | needs: [validate-release, release-sbom] | |
| 57 | needs: [validate-release, release-sbom, source-acceptance, native-acceptance] | |
| 42 | 58 | name: pantry (${{ matrix.platform.name }}) |
| 43 | 59 | strategy: |
| 44 | 60 | fail-fast: false |
| @@ -673,7 +689,7 @@ jobs: | ||
| 673 | 689 | |
| 674 | 690 | publish-release: |
| 675 | 691 | name: publish complete verified release |
| 676 | needs: [validate-release, verify-release, verify-macos-downloads, verify-desktop-downloads, attach-release-sbom, prepare-npm] | |
| 692 | needs: [validate-release, source-acceptance, native-acceptance, verify-release, verify-macos-downloads, verify-desktop-downloads, attach-release-sbom, prepare-npm] | |
| 677 | 693 | runs-on: ubuntu-latest |
| 678 | 694 | permissions: |
| 679 | 695 | contents: write |
| @@ -196,11 +196,48 @@ test('every publishing path depends on release identity validation', () => { | ||
| 196 | 196 | expect(release.jobs.npm.if).toBe("$") |
| 197 | 197 | }) |
| 198 | 198 | |
| 199 | test('tag publication requires the complete source and native acceptance workflows', () => { | |
| 200 | for (const [name, file] of [['source-acceptance', 'ci.yml'], ['native-acceptance', 'mobile-e2e.yml']]) { | |
| 201 | const gate = release.jobs[name] | |
| 202 | expect(gate.uses).toBe(`./.github/workflows/${file}`) | |
| 203 | expect(needs(gate)).toEqual(['validate-release']) | |
| 204 | expect(gate.if).toBeUndefined() | |
| 205 | expect(gate.with).toBeUndefined() // A release cannot request a focused suite. | |
| 206 | const called = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows', file), 'utf8')) as { on: Record<string, unknown> } | |
| 207 | expect(called.on.workflow_call).toEqual({}) // No selectors or diagnostic inputs are accepted by callers. | |
| 208 | } | |
| 209 | for (const name of ['pantry', 'publish-release']) { | |
| 210 | expect(needs(release.jobs[name])).toContain('source-acceptance') | |
| 211 | expect(needs(release.jobs[name])).toContain('native-acceptance') | |
| 212 | expect(release.jobs[name].if).toBeUndefined() | |
| 213 | } | |
| 214 | function prerequisites(name: string, found = new Set<string>()): Set<string> { | |
| 215 | for (const dependency of needs(release.jobs[name])) { | |
| 216 | if (found.has(dependency)) continue | |
| 217 | found.add(dependency) | |
| 218 | prerequisites(dependency, found) | |
| 219 | } | |
| 220 | return found | |
| 221 | } | |
| 222 | for (const name of ['pantry', 'publish-release', 'publish-pantry', 'npm', 'notify-registry']) { | |
| 223 | expect(prerequisites(name).has('source-acceptance')).toBe(true) | |
| 224 | expect(prerequisites(name).has('native-acceptance')).toBe(true) | |
| 225 | } | |
| 226 | }) | |
| 227 | ||
| 228 | test('called source and native acceptance have independent concurrency groups', () => { | |
| 229 | const parse = (file: string): { concurrency: { group: string } } => Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows', file), 'utf8')) | |
| 230 | const source = parse('ci.yml').concurrency.group | |
| 231 | const native = parse('mobile-e2e.yml').concurrency.group | |
| 232 | expect(source.startsWith('source-ci-')).toBe(true) | |
| 233 | expect(native.startsWith('mobile-e2e-')).toBe(true) | |
| 234 | }) | |
| 235 | ||
| 199 | 236 | test('registry indexing waits for the public release and all of its verification gates', () => { |
| 200 | 237 | const notify = release.jobs['notify-registry'] |
| 201 | 238 | expect(notify).toBeDefined() |
| 202 | 239 | expect(needs(notify)).toEqual(['publish-release', 'publish-pantry']) |
| 203 | expect(needs(release.jobs['publish-release']).sort()).toEqual(['attach-release-sbom', 'prepare-npm', 'validate-release', 'verify-desktop-downloads', 'verify-macos-downloads', 'verify-release']) | |
| 240 | expect(needs(release.jobs['publish-release']).sort()).toEqual(['attach-release-sbom', 'native-acceptance', 'prepare-npm', 'source-acceptance', 'validate-release', 'verify-desktop-downloads', 'verify-macos-downloads', 'verify-release']) | |
| 204 | 241 | expect(release.jobs['publish-release'].if).toBeUndefined() // A failed or skipped gate cannot publish a draft. |
| 205 | 242 | expect(notify.if).toBeUndefined() // Default success gating: never bypass failed prerequisites. |
| 206 | 243 | expect(steps(notify)).toContain('https://registry.pantry.dev/api/rebuild') |