fix(functions): rebuild stale dist + predeploy build hook (FUNCTIONS-DIST-STALE)

The phrase-change 🔑 alert never fired live because the deployed artifact
didn't contain it — functions/dist is committed and is what deploys, and I
changed functions/src without rebuilding it. My own gates were green and
irrelevant: tsc --noEmit emits nothing, ts-jest compiles in memory. The user
deployed twice, faithfully, and shipped my stale artifact both times. "git
status clean" is not "deployable current".

Two fixes: dist rebuilt (diff confirms exactly one stale function,
onCoupleKeyRotated — the phrase edge), and firebase.json now carries a
predeploy hook (npm run build) so every functions deploy compiles first —
the mistake class is structurally gone rather than remembered. Landmine
written up as FUNCTIONS-DIST-STALE in the manual: when verifying "is X
deployed", compare against dist/, not src/ — src is the intent, dist is
the truth.

Functions tests green (108).
This commit is contained in:
null 2026-07-16 01:29:20 -05:00
parent 65dbd1a9c9
commit 8a9bd41fe5
6 changed files with 64 additions and 12 deletions

View File

@ -1472,6 +1472,12 @@ OOB code = `truncate6(SHA-256(pubkey ‖ nonce))`.
**Fix (R30, C1)**: `GamePromptController.consumeForSession(sessionId)` is called from every game's `enter/leave` (the same `ActiveGameSessionMonitor` hook the foreground-suppression check uses), and the controller replaces stale banners on a new start (last-wins by timestamp) so a banner for a session the user is already inside is never shown. **Fix (R30, C1)**: `GamePromptController.consumeForSession(sessionId)` is called from every game's `enter/leave` (the same `ActiveGameSessionMonitor` hook the foreground-suppression check uses), and the controller replaces stale banners on a new start (last-wins by timestamp) so a banner for a session the user is already inside is never shown.
**Re-introduction risk**: any new entry point into a game (a deep link, a partner-joined push, a "rematch?" CTA) that bypasses `ActiveGameSessionMonitor.enter` and therefore doesn't call `consumeForSession` will leave the previous banner alive - and the next push will look like a duplicate. The B6c split of `onGameSessionUpdate` into per-game part-finished triggers means a new game also needs a per-game monitor hook, not a shared one. Verified live across all four session games post-R30. **Re-introduction risk**: any new entry point into a game (a deep link, a partner-joined push, a "rematch?" CTA) that bypasses `ActiveGameSessionMonitor.enter` and therefore doesn't call `consumeForSession` will leave the previous banner alive - and the next push will look like a duplicate. The B6c split of `onGameSessionUpdate` into per-game part-finished triggers means a new game also needs a per-game monitor hook, not a shared one. Verified live across all four session games post-R30.
### FUNCTIONS-DIST-STALE - editing functions/src does NOT change what deploys (fixed with a predeploy hook)
**Symptom (2026-07-16)**: a live feature test showed the deployed `onCoupleKeyRotated` lacked code that was plainly in `functions/src` and committed — the user deployed twice and both deploys shipped the old behaviour.
**Cause**: `functions/dist` (compiled JS) is COMMITTED and is what `firebase deploy` ships; nothing rebuilt it. The change's author ran `tsc --noEmit` (checks, emits nothing) and `npm test` (ts-jest, compiles in-memory) — both green, both irrelevant to the artifact. `git status` clean ≠ deployable current.
**Fix**: `firebase.json` now has `predeploy: npm --prefix $RESOURCE_DIR run build`, so every functions deploy compiles first — the mistake class is structurally gone.
**Re-introduction risk**: any committed build artifact with no build-on-deploy hook. If a second codebase/artifact is ever added, wire its predeploy the same day. And when verifying "is X deployed", compare against `dist/`, not `src/` — src is the intent, dist is the truth.
### FUNCTIONS-V2-DEPLOY - 2nd-gen deploys can fail mid-deploy with "Changing from HTTPS function to background triggered function" if a stray service is left behind ### FUNCTIONS-V2-DEPLOY - 2nd-gen deploys can fail mid-deploy with "Changing from HTTPS function to background triggered function" if a stray service is left behind
**Symptom (2026-07-08)**: a deploy that swapped a Cloud Function from HTTPS to Firestore-triggered (e.g. promoting a callable to a scheduled job, or splitting a function module) failed with *"Changing from an HTTPS function to a background triggered function is not allowed"*. A first-ever v2 deploy can also fail with Eventarc service-agent IAM propagation races - the *"retry in a few minutes"* failures. **Symptom (2026-07-08)**: a deploy that swapped a Cloud Function from HTTPS to Firestore-triggered (e.g. promoting a callable to a scheduled job, or splitting a function module) failed with *"Changing from an HTTPS function to a background triggered function is not allowed"*. A first-ever v2 deploy can also fail with Eventarc service-agent IAM propagation races - the *"retry in a few minutes"* failures.
**Root cause**: an orphaned `https` service (from a partial deploy or a previous attempt) is blocking the new background trigger from being created under the same name. The 2nd-gen quota was the proximate cause of the *initial* deploy failure (Cloud Run CPU quota couldn't fit ~35 services at v2's default 1 vCPU/instance; container healthchecks failed with *"Quota exceeded for total allowable CPU per project per region"*) - `functions/src/options.ts` pins `cpu: 'gcf_gen1'`, `concurrency: 1`, `maxInstances: 5` as the workaround. **Root cause**: an orphaned `https` service (from a partial deploy or a previous attempt) is blocking the new background trigger from being created under the same name. The 2nd-gen quota was the proximate cause of the *initial* deploy failure (Cloud Run CPU quota couldn't fit ~35 services at v2's default 1 vCPU/instance; container healthchecks failed with *"Quota exceeded for total allowable CPU per project per region"*) - `functions/src/options.ts` pins `cpu: 'gcf_gen1'`, `concurrency: 1`, `maxInstances: 5` as the workaround.

View File

@ -21,6 +21,9 @@
".git", ".git",
"firebase-debug.log", "firebase-debug.log",
"firebase-debug.*.log" "firebase-debug.*.log"
],
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run build"
] ]
} }
] ]

View File

@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.onCoupleKeyRotated = void 0; exports.onCoupleKeyRotated = void 0;
exports.isKeyGenerationIncrease = isKeyGenerationIncrease; exports.isKeyGenerationIncrease = isKeyGenerationIncrease;
exports.isPhrasePublished = isPhrasePublished;
const admin = __importStar(require("firebase-admin")); const admin = __importStar(require("firebase-admin"));
const firestore_1 = require("firebase-functions/v2/firestore"); const firestore_1 = require("firebase-functions/v2/firestore");
const queueAndPush_1 = require("../notifications/queueAndPush"); const queueAndPush_1 = require("../notifications/queueAndPush");
@ -55,26 +56,51 @@ function isKeyGenerationIncrease(before, after) {
const a = typeof after === 'number' ? after : 0; const a = typeof after === 'number' ? after : 0;
return a > b; return a > b;
} }
/**
* Pure edge guard for a newly PUBLISHED recovery phrase (phase 1 of the change handshake).
*
* Deliberately keyed on `phraseGeneration`, not `phraseWrapGeneration`: the publish is the moment the
* partner needs to act on, and their ack is what lets the change finish at all. Same shape as
* isKeyGenerationIncrease so the two read alike.
*/
function isPhrasePublished(before, after) {
const b = typeof before === 'number' ? before : 0;
const a = typeof after === 'number' ? after : 0;
return a > b;
}
exports.onCoupleKeyRotated = (0, firestore_1.onDocumentUpdated)('couples/{coupleId}', async (event) => { exports.onCoupleKeyRotated = (0, firestore_1.onDocumentUpdated)('couples/{coupleId}', async (event) => {
var _a, _b, _c; var _a, _b, _c;
const before = (_a = event.data) === null || _a === void 0 ? void 0 : _a.before.data(); const before = (_a = event.data) === null || _a === void 0 ? void 0 : _a.before.data();
const after = (_b = event.data) === null || _b === void 0 ? void 0 : _b.after.data(); const after = (_b = event.data) === null || _b === void 0 ? void 0 : _b.after.data();
if (!isKeyGenerationIncrease(before === null || before === void 0 ? void 0 : before.keyGeneration, after === null || after === void 0 ? void 0 : after.keyGeneration)) const rotated = isKeyGenerationIncrease(before === null || before === void 0 ? void 0 : before.keyGeneration, after === null || after === void 0 ? void 0 : after.keyGeneration);
const phrasePublished = isPhrasePublished(before === null || before === void 0 ? void 0 : before.phraseGeneration, after === null || after === void 0 ? void 0 : after.phraseGeneration);
if (!rotated && !phrasePublished)
return; return;
const { coupleId } = event.params; const { coupleId } = event.params;
const db = admin.firestore(); const db = admin.firestore();
const userIds = ((_c = after === null || after === void 0 ? void 0 : after.userIds) !== null && _c !== void 0 ? _c : []); const userIds = ((_c = after === null || after === void 0 ? void 0 : after.userIds) !== null && _c !== void 0 ? _c : []);
if (userIds.length === 0) if (userIds.length === 0)
return; return;
log_1.logger.log(`[onCoupleKeyRotated] couple=${coupleId} generation=${after === null || after === void 0 ? void 0 : after.keyGeneration}`); // A rotation and a phrase publish are separate write shapes (the rules forbid combining them), so
// Per-member isolation: one failed send must not cost the other member their alert. // exactly one of these is true per event.
const results = await Promise.allSettled(userIds.map((uid) => (0, queueAndPush_1.queueAndPush)(db, uid, { const alert = rotated
? {
type: 'couple_key_rotated', type: 'couple_key_rotated',
title: '🔑 Security update', title: '🔑 Security update',
body: 'Your shared key was rotated. Open the app and everything continues as normal.', body: 'Your shared key was rotated. Open the app and everything continues as normal.',
coupleId, }
bypassQuietHours: true, : {
}))); // Not merely informational: the change CANNOT complete until the partner's app opens and
// confirms it can read the new phrase. This push is what gets them there. And if they didn't
// expect a phrase change, this is how they find out — the was-this-you philosophy again.
type: 'recovery_phrase_changed',
title: '🔑 Security update',
body: 'Your recovery phrase was changed. Open the app to finish saving the new one.',
};
log_1.logger.log(`[onCoupleKeyRotated] couple=${coupleId} ` +
`${rotated ? `generation=${after === null || after === void 0 ? void 0 : after.keyGeneration}` : `phraseGeneration=${after === null || after === void 0 ? void 0 : after.phraseGeneration}`}`);
// Per-member isolation: one failed send must not cost the other member their alert.
const results = await Promise.allSettled(userIds.map((uid) => (0, queueAndPush_1.queueAndPush)(db, uid, Object.assign(Object.assign({}, alert), { coupleId, bypassQuietHours: true }))));
results.forEach((r, i) => { results.forEach((r, i) => {
if (r.status === 'rejected') { if (r.status === 'rejected') {
log_1.logger.warn('[onCoupleKeyRotated] alert failed', { uid: userIds[i], error: String(r.reason) }); log_1.logger.warn('[onCoupleKeyRotated] alert failed', { uid: userIds[i], error: String(r.reason) });

View File

@ -1 +1 @@
{"version":3,"file":"onCoupleKeyRotated.js","sourceRoot":"","sources":["../../src/couples/onCoupleKeyRotated.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA,0DAIC;AArBD,sDAAuC;AACvC,+DAAmE;AACnE,gEAA4D;AAC5D,gCAA+B;AAE/B;;;;;;;;;GASG;AAEH,qGAAqG;AACrG,SAAgB,uBAAuB,CAAC,MAAe,EAAE,KAAc;IACrE,MAAM,CAAC,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;IACjD,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAEY,QAAA,kBAAkB,GAAG,IAAA,6BAAiB,EAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;;IACxF,MAAM,MAAM,GAAG,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,CAAC,IAAI,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,MAAA,KAAK,CAAC,IAAI,0CAAE,KAAK,CAAC,IAAI,EAAE,CAAA;IACtC,IAAI,CAAC,uBAAuB,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,aAAa,EAAE,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,aAAa,CAAC;QAAE,OAAM;IAEjF,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;IACjC,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAG,CAAC,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,OAAO,mCAAI,EAAE,CAAa,CAAA;IAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAEhC,YAAM,CAAC,GAAG,CAAC,+BAA+B,QAAQ,eAAe,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,aAAa,EAAE,CAAC,CAAA;IAExF,oFAAoF;IACpF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAClB,IAAA,2BAAY,EAAC,EAAE,EAAE,GAAG,EAAE;QACpB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,+EAA+E;QACrF,QAAQ;QACR,gBAAgB,EAAE,IAAI;KACvB,CAAC,CACH,CACF,CAAA;IACD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC5B,YAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAChG,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} {"version":3,"file":"onCoupleKeyRotated.js","sourceRoot":"","sources":["../../src/couples/onCoupleKeyRotated.ts"],"names":[],"mappings":"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"}

View File

@ -21,4 +21,21 @@ describe('isKeyGenerationIncrease', () => {
expect((0, onCoupleKeyRotated_1.isKeyGenerationIncrease)(null, 'high')).toBe(false); expect((0, onCoupleKeyRotated_1.isKeyGenerationIncrease)(null, 'high')).toBe(false);
}); });
}); });
describe('isPhrasePublished', () => {
it('fires when a new recovery phrase is published (phase 1)', () => {
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(undefined, 1)).toBe(true);
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(0, 1)).toBe(true);
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(2, 3)).toBe(true);
});
it('ignores the acks and the completion — only the publish needs the partner in the app', () => {
// Phase 2 (acks) and phase 3 (the re-wrap) leave phraseGeneration alone, so they must not alert:
// the partner is already acting, and re-alerting them would be noise on a security channel.
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(1, 1)).toBe(false);
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(undefined, undefined)).toBe(false);
});
it('never fires on a downgrade or a redelivered stale event', () => {
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(5, 4)).toBe(false);
expect((0, onCoupleKeyRotated_1.isPhrasePublished)(1, undefined)).toBe(false);
});
});
//# sourceMappingURL=onCoupleKeyRotated.test.js.map //# sourceMappingURL=onCoupleKeyRotated.test.js.map

View File

@ -1 +1 @@
{"version":3,"file":"onCoupleKeyRotated.test.js","sourceRoot":"","sources":["../../src/couples/onCoupleKeyRotated.test.ts"],"names":[],"mappings":";;AAAA,6DAA8D;AAE9D,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,IAAA,4CAAuB,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,uGAAuG;QACvG,MAAM,CAAC,IAAA,4CAAuB,EAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjE,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mGAAmG,EAAE,GAAG,EAAE;QAC3G,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,IAAA,4CAAuB,EAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,IAAA,4CAAuB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} {"version":3,"file":"onCoupleKeyRotated.test.js","sourceRoot":"","sources":["../../src/couples/onCoupleKeyRotated.test.ts"],"names":[],"mappings":";;AAAA,6DAAiF;AAEjF,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,IAAA,4CAAuB,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,uGAAuG;QACvG,MAAM,CAAC,IAAA,4CAAuB,EAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjE,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mGAAmG,EAAE,GAAG,EAAE;QAC3G,MAAM,CAAC,IAAA,4CAAuB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,IAAA,4CAAuB,EAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,IAAA,4CAAuB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,IAAA,sCAAiB,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClD,MAAM,CAAC,IAAA,sCAAiB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1C,MAAM,CAAC,IAAA,sCAAiB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,iGAAiG;QACjG,4FAA4F;QAC5F,MAAM,CAAC,IAAA,sCAAiB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAA,sCAAiB,EAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,IAAA,sCAAiB,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAA,sCAAiB,EAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}