-
Notifications
You must be signed in to change notification settings - Fork 4
test(release): complete credential-free brand pilot #436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: xuan/code-559
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| { | ||
| "brandBuildMatrixVersion": 1, | ||
| "brands": [ | ||
| { | ||
| "brandId": "acme", | ||
| "channel": "canary", | ||
| "compliance": { | ||
| "android": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| }, | ||
| "desktop": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| }, | ||
| "ios": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| } | ||
| }, | ||
| "distribution": { | ||
| "desktop": null, | ||
| "mobile": null | ||
| }, | ||
| "releaseManifests": { | ||
| "android": { | ||
| "brandId": "acme", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "0675b1b33e81d4898f75233fdf9bda7243348e286ebfd3b06f807d82fff8818f", | ||
| "platform": "android", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://acme.example.invalid/telemetry" | ||
| }, | ||
| "desktop": { | ||
| "brandId": "acme", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "936250a3ef922cede3a200b5dc401cc7697ee1db90dc3efd0f873358524f01e3", | ||
| "platform": "desktop", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://acme.example.invalid/telemetry" | ||
| }, | ||
| "ios": { | ||
| "brandId": "acme", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "a689a8d95f74d9cb00b5d9850af3ecfd50edb23d2496c71805c9ffe4659d56ae", | ||
| "platform": "ios", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://acme.example.invalid/telemetry" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "brandId": "zenith", | ||
| "channel": "canary", | ||
| "compliance": { | ||
| "android": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| }, | ||
| "desktop": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| }, | ||
| "ios": { | ||
| "checklist": { | ||
| "configurableFeaturesDisclosed": true, | ||
| "dataPracticesReviewed": true, | ||
| "noExecutableCode": true, | ||
| "permissionsReviewed": true, | ||
| "storeMetadataReviewed": true | ||
| }, | ||
| "disclosedFeatures": [ | ||
| "feature.aiAssist", | ||
| "feature.newEditor", | ||
| "modules.messaging.enabled", | ||
| "modules.terminal.enabled", | ||
| "modules.workspace.enabled" | ||
| ] | ||
| } | ||
| }, | ||
| "distribution": { | ||
| "desktop": null, | ||
| "mobile": null | ||
| }, | ||
| "releaseManifests": { | ||
| "android": { | ||
| "brandId": "zenith", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "a0ef5196645ae3b857343784f7a5ab5d6f5184b15c7cb646d8e86c93ff5384b0", | ||
| "platform": "android", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://zenith.example.invalid/telemetry" | ||
| }, | ||
| "desktop": { | ||
| "brandId": "zenith", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "99a93cec0ca5381faa15a5def6727736f220b5d7d111e1fce04afda1d321aef2", | ||
| "platform": "desktop", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://zenith.example.invalid/telemetry" | ||
| }, | ||
| "ios": { | ||
| "brandId": "zenith", | ||
| "channel": "canary", | ||
| "configRevisionId": "code-561-operational", | ||
| "expectedSnapshotSha256": "e1b93b64973e0192ed2e1d8ba9a4cca27ae2bb5521ef6011392c2d86b510b95b", | ||
| "platform": "ios", | ||
| "publicKeyringsSha256": "1a674a4c47d1ef57e51f7e50e8f044f32cffa0450574f12f23ddeb5cb619d445", | ||
| "publisherGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "releaseManifestFormatVersion": 1, | ||
| "revisionSha256": "e8389e2edc8273c5ec1b029c7101ae18af6e5b55218f0c8fd2dfe640cf695c5b", | ||
| "sourceGitSha": "e4a0624abbc8ed1cac4948fa90239176a83cb96e", | ||
| "telemetryEndpoint": "https://zenith.example.invalid/telemetry" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ const RE_DIVERGENT_SOURCE = /all platforms must share sourceGitSha/; | |
| const RE_SHARED_DESTINATION = /R2 prefixes in one bucket must not overlap/; | ||
| const RE_SHARED_CREDENTIALS = /credentialSecretPrefix: must be unique/; | ||
| const RE_SHARED_APP_STORE_APP = /ios\.ascAppId: must be unique/; | ||
| const ACTIONS_EXPRESSION = String.fromCodePoint(36); | ||
|
|
||
| function sha(character) { | ||
| return character.repeat(64); | ||
|
|
@@ -81,6 +82,31 @@ function matrix(...brands) { | |
| } | ||
|
|
||
| describe('parseBrandBuildMatrix', () => { | ||
| it('pins the CODE-561 credential-free pilot to two brands and all platforms', async () => { | ||
| const pilot = JSON.parse( | ||
| await readFile( | ||
| new URL('../release/brand-matrices/code-561-pilot.json', import.meta.url), | ||
| 'utf8', | ||
| ), | ||
| ); | ||
| const plan = buildMatrixPlan(pilot); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| expect(plan.targets.include.map(({ brandId, platform }) => `${brandId}/${platform}`)).toEqual([ | ||
| 'acme/desktop', | ||
| 'acme/ios', | ||
| 'acme/android', | ||
| 'zenith/desktop', | ||
| 'zenith/ios', | ||
| 'zenith/android', | ||
| ]); | ||
| expect( | ||
| new Set(pilot.brands.map((entry) => entry.releaseManifests.desktop.publisherGitSha)), | ||
| ).toEqual(new Set(['e4a0624abbc8ed1cac4948fa90239176a83cb96e'])); | ||
| expect( | ||
| pilot.brands.every((entry) => Object.values(entry.distribution).every((x) => x === null)), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it('builds the complete brand by platform plan', () => { | ||
| const input = matrix(brand('acme'), brand('zenith')); | ||
| const plan = buildMatrixPlan(input); | ||
|
|
@@ -226,3 +252,117 @@ describe('parseBrandBuildMatrix', () => { | |
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('release brand matrix workflow', () => { | ||
| it('keeps local runtime validation independent of provider and signing inputs', async () => { | ||
| const workflow = await readFile( | ||
| new URL('../workflows/release-brand-matrix.yml', import.meta.url), | ||
| 'utf8', | ||
| ); | ||
| const validation = workflow.slice( | ||
| workflow.indexOf(' credential-free-validation:'), | ||
| workflow.indexOf(' release-environment-preflight:'), | ||
| ); | ||
|
|
||
| expect(validation).toContain('needs: prepare'); | ||
| expect(validation).toContain( | ||
| `matrix: ${ACTIONS_EXPRESSION}{{ fromJSON(needs.prepare.outputs.targets) }}`, | ||
| ); | ||
| expect(validation).toContain('xvfb-run -a pnpm -F @linkcode/desktop e2e:config-canary'); | ||
| expect(validation).toContain('pnpm -F @linkcode/mobile smoke:export'); | ||
| expect(validation).toContain( | ||
| `expo prebuild --clean --no-install --platform '${ACTIONS_EXPRESSION}{{ matrix.platform }}'`, | ||
| ); | ||
| expect(validation).toContain("matrix.platform == 'desktop'"); | ||
| expect(validation).toContain("matrix.platform != 'desktop'"); | ||
| expect(validation).toContain( | ||
| `credential-free-${ACTIONS_EXPRESSION}{{ matrix.brandId }}-${ACTIONS_EXPRESSION}{{ matrix.platform }}`, | ||
| ); | ||
| expect(validation).toContain('"local-static-origin"'); | ||
| expect(validation).toContain('providerDeploymentId:null'); | ||
| expect(validation).not.toContain('environment: release'); | ||
| expect(validation).not.toContain('secrets.'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| expect(validation).not.toContain('release-environment-preflight'); | ||
| }); | ||
|
|
||
| it('fails closed unless the release environment is protected', async () => { | ||
| const workflow = await readFile( | ||
| new URL('../workflows/release-brand-matrix.yml', import.meta.url), | ||
| 'utf8', | ||
| ); | ||
| const preflight = workflow.slice( | ||
| workflow.indexOf(' release-environment-preflight:'), | ||
| workflow.indexOf(' render-inputs:'), | ||
| ); | ||
|
|
||
| expect(preflight).not.toContain('environment:'); | ||
| expect(preflight).toContain('protection_rules'); | ||
| expect(preflight).toContain('required_reviewers'); | ||
| expect(preflight).toContain('deployment_branch_policy'); | ||
| expect(preflight).toContain('gh api "repos/$GITHUB_REPOSITORY/environments/release"'); | ||
| expect(preflight).toContain('secrets.RELEASE_ENVIRONMENT_ADMIN_TOKEN'); | ||
| expect(preflight).toContain('inputs.build'); | ||
| const renderInputs = workflow.slice( | ||
| workflow.indexOf(' render-inputs:'), | ||
| workflow.indexOf(' signing-inputs:'), | ||
| ); | ||
| expect(renderInputs).toContain('needs: [prepare, release-environment-preflight]'); | ||
| expect(renderInputs).toContain('environment: release'); | ||
| const signingInputs = workflow.slice( | ||
| workflow.indexOf(' signing-inputs:'), | ||
| workflow.indexOf(' render:'), | ||
| ); | ||
| expect(signingInputs).toContain('needs: [prepare, release-environment-preflight]'); | ||
| expect(signingInputs).toContain('environment: release'); | ||
| expect(workflow.split(' environment: release')).toHaveLength(7); | ||
| expect(workflow.split('release_environment: release')).toHaveLength(3); | ||
| }); | ||
|
|
||
| it('passes the release environment through reusable signing workflows', async () => { | ||
| const [desktop, mobile] = await Promise.all([ | ||
| readFile(new URL('../workflows/build-desktop.yml', import.meta.url), 'utf8'), | ||
| readFile(new URL('../workflows/build-mobile.yml', import.meta.url), 'utf8'), | ||
| ]); | ||
|
|
||
| expect(desktop).toContain('release_environment:'); | ||
| expect(desktop).toContain( | ||
| `environment: ${ACTIONS_EXPRESSION}{{ inputs.release_environment || 'release' }}`, | ||
| ); | ||
| expect(desktop).toContain( | ||
| `environment: ${ACTIONS_EXPRESSION}{{ inputs.sign && (inputs.release_environment || 'release') || '' }}`, | ||
| ); | ||
| expect(mobile).toContain('release_environment:'); | ||
| expect( | ||
| mobile.split( | ||
| `environment: ${ACTIONS_EXPRESSION}{{ inputs.release_environment || 'release' }}`, | ||
| ), | ||
| ).toHaveLength(4); | ||
| }); | ||
|
|
||
| it('binds credential-free desktop recovery evidence to immutable release inputs', async () => { | ||
| const workflow = await readFile( | ||
| new URL('../workflows/release-brand-matrix.yml', import.meta.url), | ||
| 'utf8', | ||
| ); | ||
| const desktopValidation = workflow.slice( | ||
| workflow.indexOf(' desktop-validation:'), | ||
| workflow.indexOf(' mobile-validation:'), | ||
| ); | ||
|
|
||
| expect(desktopValidation).toContain('inputs.build && !inputs.sign'); | ||
| expect(desktopValidation).toContain('xvfb-run -a pnpm -F @linkcode/desktop e2e:config-canary'); | ||
| expect(desktopValidation).toContain( | ||
| '54ce1fc855e12295a8dd1490463c9afac8e84a526f1e16340bcefe4f0fec8e39', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing in the suite ties this literal to the actual fixture. The test asserts the workflow contains the hash, and the workflow asserts the file matches it — but that second check only runs on a manual release dispatch, which per the PR description is not happening yet. So editing
const fixture = await readFile(
new URL('../../apps/desktop/e2e/fixtures/pilot-e2e-v1.json', import.meta.url),
);
expect(createHash('sha256').update(fixture).digest('hex')).toBe(
'54ce1fc855e12295a8dd1490463c9afac8e84a526f1e16340bcefe4f0fec8e39',
); |
||
| ); | ||
| expect(desktopValidation).toContain('"normal":["1","2","3","4"]'); | ||
| expect(desktopValidation).toContain('"emergency":["1","2","3"]'); | ||
| expect(desktopValidation).toContain('"kind":"local-static-origin"'); | ||
| expect(desktopValidation).toContain('"providerDeploymentId":null'); | ||
| expect(desktopValidation).toContain('--expected-delivery-sha256'); | ||
| expect(desktopValidation).toContain( | ||
| '--release-manifest release-inputs/release-manifest.desktop.json', | ||
| ); | ||
| expect(desktopValidation).toContain('--out release-provenance.desktop.json'); | ||
| expect(desktopValidation).not.toContain('environment: release'); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A null
distributionmakes this matrix unusable for anybuild=truedispatch —brand-matrix.cjs:232fails withdesktop and mobile delivery inputs are required when build=true, which I reproduced against this exact file. Sincematrix_jsonis rejected for builds (release-brand-matrix.yml:77-80), this is the only committed matrix a build could use, sorelease-environment-preflight,render,desktop-validationandmobile-validationare all unreachable and onlycredential-free-validationever runs.Technical details