Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/officers/LOW_COST_COMMUNITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ The #683 source change configured no budget, spending cap, artifact-cleanup poli

## Engineering staging profiles — #701

The first protected attempt built and activated signup, but the hosted signup test exposed missing explicit SDK project context. #703 repairs that exact partial release and the provider's empty no-retry representation. Partial deployment is not proof of a working profile service. Completed proof still requires both Functions and the hosted checks below; no permission widening or production fallback is allowed.

**Purpose:** prove only profile creation and recovery using made-up staging accounts. This is not membership approval, event management, a payment release or a public website launch.
**Approver:** Dave Liu, sole release/recovery owner. Administrator bypass is disabled; normal self-approval remains permitted.
**Availability:** **NOT AVAILABLE YET** until #701 records exact merged source, protected deployment, actual settings and hosted checks. #113/#133/#136 remain unresolved for production; this isolated engineering proof does not waive them.
Expand Down
28 changes: 21 additions & 7 deletions scripts/staging-profile-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ function functionSpec(target, sourceSha, bundleDigest, uploadUrl) {
sourceUploadUrl: uploadUrl, availableMemoryMb: 256, timeout: '30s', minInstances: 0, maxInstances: 2,
ingressSettings: 'ALLOW_ALL',
labels: { 'managed-by': 'mprc-profile-stage', 'source-sha': sourceSha, 'bundle-a': bundleDigest.slice(0, 32), 'bundle-b': bundleDigest.slice(32) },
environmentVariables: { FIREBASE_CONFIG: JSON.stringify({ projectId: PROJECT }), ENFORCE_APP_CHECK: 'true', COMMERCE_ENABLED: 'false' },
environmentVariables: { FIREBASE_CONFIG: JSON.stringify({ projectId: PROJECT }), GCLOUD_PROJECT: PROJECT, ENFORCE_APP_CHECK: 'true', COMMERCE_ENABLED: 'false' },
...(target === 'ensureMemberProfile'
? { httpsTrigger: { securityLevel: 'SECURE_ALWAYS' } }
: { eventTrigger: { eventType: 'providers/firebase.auth/eventTypes/user.create', resource: `projects/${PROJECT}`, service: 'firebaseauth.googleapis.com' } }),
};
}

function validateHostedFunction(actual, expected, { ready = true } = {}) {
function validateHostedFunction(actual, expected, { ready = true, previousInitialRelease = false } = {}) {
check(actual && actual.name === expected.name);
for (const field of ['entryPoint', 'runtime', 'serviceAccountEmail', 'buildServiceAccount', 'dockerRepository', 'availableMemoryMb', 'timeout', 'maxInstances', 'ingressSettings']) check(actual[field] === expected[field]);
// Protobuf omits a configured numeric zero, but not other required settings.
Expand All @@ -95,15 +95,29 @@ function validateHostedFunction(actual, expected, { ready = true } = {}) {
check(actual.status === 'ACTIVE');
for (const [key, value] of Object.entries(expected.labels)) check(actual.labels[key] === value);
}
check(JSON.stringify(actual.environmentVariables) === JSON.stringify(expected.environmentVariables)
|| Object.keys(actual.environmentVariables || {}).length === Object.keys(expected.environmentVariables).length
&& Object.entries(expected.environmentVariables).every(([key, value]) => actual.environmentVariables[key] === value));
// Only the exact partial #702 signup release may lack the SDK project
// context. Admit it for same-bundle repair, never as completed proof.
const acceptedEnvironment = !ready && previousInitialRelease
&& actual.name.endsWith('/createMemberOnSignUp')
&& actual.labels?.['source-sha'] === '3edffdf51ccef248d83e2c5c8600c3628d2e0ae1'
&& actual.labels?.['bundle-a'] === expected.labels['bundle-a']
&& actual.labels?.['bundle-b'] === expected.labels['bundle-b']
&& actual.environmentVariables?.GCLOUD_PROJECT === undefined
? Object.fromEntries(Object.entries(expected.environmentVariables).filter(([key]) => key !== 'GCLOUD_PROJECT'))
: expected.environmentVariables;
check(Object.keys(actual.environmentVariables || {}).length === Object.keys(acceptedEnvironment).length
&& Object.entries(acceptedEnvironment).every(([key, value]) => actual.environmentVariables[key] === value));
check(!actual.secretEnvironmentVariables?.length && !actual.secretVolumes?.length && !actual.vpcConnector);
if (expected.httpsTrigger) {
check(actual.httpsTrigger?.securityLevel === 'SECURE_ALWAYS' && !actual.eventTrigger);
} else {
check(actual.eventTrigger?.eventType === expected.eventTrigger.eventType && actual.eventTrigger?.resource === expected.eventTrigger.resource && !actual.httpsTrigger);
check(!actual.eventTrigger.failurePolicy); // Same non-retrying background source behavior.
// The v1 service also returns an empty FailurePolicy message for no retry.
// A present retry message, including retry: {}, means retry is enabled.
const failurePolicy = actual.eventTrigger.failurePolicy;
check(failurePolicy === undefined || (failurePolicy !== null
&& typeof failurePolicy === 'object' && !Array.isArray(failurePolicy)
&& Object.keys(failurePolicy).length === 0));
}
}

Expand Down Expand Up @@ -144,7 +158,7 @@ async function release(env) {
check(Array.isArray(existing) && existing.length <= 2);
for (const fn of existing) {
const target = fn.name?.split('/').at(-1);
validateHostedFunction(fn, functionSpec(target, env.GITHUB_SHA, bundle.digest, 'https://storage.googleapis.com/synthetic'), { ready: false });
validateHostedFunction(fn, functionSpec(target, env.GITHUB_SHA, bundle.digest, 'https://storage.googleapis.com/synthetic'), { ready: false, previousInitialRelease: true });
}
for (const target of TARGETS) {
await verifyCurrentSource(env); // Recheck immediately before each write.
Expand Down
24 changes: 23 additions & 1 deletion tests/staging-profile-authority.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ test('two first-generation specs use explicit identities, limits and no secrets/
assert.match(spec.serviceAccountEmail, /^mprc-profile-runtime@run-mprc-staging\./);
assert.match(spec.buildServiceAccount, /^projects\/run-mprc-staging\/serviceAccounts\/mprc-profile-build@/);
assert.equal(spec.environmentVariables.COMMERCE_ENABLED, 'false');
assert.equal(spec.environmentVariables.GCLOUD_PROJECT, 'run-mprc-staging');
assert.equal(JSON.parse(spec.environmentVariables.FIREBASE_CONFIG).projectId, 'run-mprc-staging');
assert.ok(!spec.secretEnvironmentVariables && !spec.secretVolumes);
if (target === 'ensureMemberProfile') assert.equal(spec.httpsTrigger.securityLevel, 'SECURE_ALWAYS');
else assert.equal(spec.eventTrigger.resource, 'projects/run-mprc-staging');
Expand All @@ -234,7 +236,13 @@ test('hosted resource readback rejects broad identities, foreign config, missing
for (const [field, value] of Object.entries({ runtime: 'nodejs20', serviceAccountEmail: 'default', buildServiceAccount: 'default', minInstances: 1, maxInstances: undefined, availableMemoryMb: 512, timeout: '540s', status: 'DEPLOY_IN_PROGRESS', secretEnvironmentVariables: [{}], vpcConnector: 'foreign', labels: {}, environmentVariables: { ...expected.environmentVariables, COMMERCE_ENABLED: 'true' } })) {
assert.throws(() => release.validateHostedFunction({ ...actual, [field]: value }, expected));
}
if (target === 'createMemberOnSignUp') assert.throws(() => release.validateHostedFunction({ ...actual, eventTrigger: { ...expected.eventTrigger, resource: 'projects/production' } }, expected));
if (target === 'createMemberOnSignUp') {
release.validateHostedFunction({ ...actual, eventTrigger: { ...expected.eventTrigger, failurePolicy: {} } }, expected);
for (const failurePolicy of [{ retry: {} }, { unexpected: true }, [], null]) {
assert.throws(() => release.validateHostedFunction({ ...actual, eventTrigger: { ...expected.eventTrigger, failurePolicy } }, expected));
}
assert.throws(() => release.validateHostedFunction({ ...actual, eventTrigger: { ...expected.eventTrigger, resource: 'projects/production' } }, expected));
}
}
});

Expand All @@ -247,6 +255,20 @@ test('runtime/build actAs is checked on exact accounts without accepting key cre
await assert.rejects(release.validateActAs('synthetic-token', mockFetch([{ permissions: ['iam.serviceAccounts.actAs', 'iam.serviceAccountKeys.create'] }])));
});

test('repair admission is limited to exact initial signup source and bundle, never hosted success', () => {
const expected = expectedFunction('createMemberOnSignUp');
const actual = { ...expected, status: 'ACTIVE', labels: { ...expected.labels, 'source-sha': '3edffdf51ccef248d83e2c5c8600c3628d2e0ae1' }, environmentVariables: { ...expected.environmentVariables }, eventTrigger: { ...expected.eventTrigger, failurePolicy: {} } };
delete actual.environmentVariables.GCLOUD_PROJECT;
release.validateHostedFunction(actual, expected, { ready: false, previousInitialRelease: true });
assert.throws(() => release.validateHostedFunction(actual, expected));
assert.throws(() => release.validateHostedFunction(actual, expected, { ready: false }));
for (const [key, value] of Object.entries({ 'source-sha': 'c'.repeat(40), 'bundle-a': 'c'.repeat(32), 'bundle-b': 'c'.repeat(32) })) {
assert.throws(() => release.validateHostedFunction({ ...actual, labels: { ...actual.labels, [key]: value } }, expected, { ready: false, previousInitialRelease: true }));
}
assert.throws(() => release.validateHostedFunction({ ...actual, environmentVariables: { ...actual.environmentVariables, GCLOUD_PROJECT: 'production' } }, expected, { ready: false, previousInitialRelease: true }));
assert.throws(() => release.validateHostedFunction({ ...actual, environmentVariables: { ...actual.environmentVariables, STRIPE_SECRET_KEY: 'synthetic' } }, expected, { ready: false, previousInitialRelease: true }));
});

test('operation polling is bounded, and failed or malformed operations never count as success', async () => {
let requests = 0;
await release.waitForOperation({ name: 'operations/synthetic' }, 'synthetic-token', async () => ({ done: ++requests === 2 }), async () => {});
Expand Down
Loading